From f515e4408c9e3801a8267e87b21c4ae7d55cbff3 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 30 Dec 2025 22:44:18 +0300 Subject: [PATCH 01/30] license and composer json normalizer --- LICENSE | 21 ++ composer.json | 10 +- composer.lock | 933 ++++++++++++++++++++++++++++++++++++++++++++++++ src/changes.php | 11 +- 4 files changed, 969 insertions(+), 6 deletions(-) create mode 100644 LICENSE create mode 100644 composer.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..95fbade --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2023-2025 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/composer.json b/composer.json index 61c6be0..b031c21 100644 --- a/composer.json +++ b/composer.json @@ -1,12 +1,12 @@ { - "name": "_____/_____", + "name": "phptg/scaffolder", "type": "project", + "require": { + "vjik/scaffolder": "dev-master" + }, "autoload": { "psr-4": { - "_____\\": "src/" + "Phptg\\Scaffolder\\": "src/" } - }, - "require": { - "vjik/scaffolder": "dev-master" } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..c2122e4 --- /dev/null +++ b/composer.lock @@ -0,0 +1,933 @@ +{ + "_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": "c728a750455d10c837150ec90cb36bab", + "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.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "fcb73f69d655b48fcb894a262f074218df08bd58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/fcb73f69d655b48fcb894a262f074218df08bd58", + "reference": "fcb73f69d655b48fcb894a262f074218df08bd58", + "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.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-05T15:25:33+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": "7b7a35f0d3a6798022d3b096e37dc0638b7d0cf6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vjik/scaffolder/zipball/7b7a35f0d3a6798022d3b096e37dc0638b7d0cf6", + "reference": "7b7a35f0d3a6798022d3b096e37dc0638b7d0cf6", + "shasum": "" + }, + "require": { + "composer/semver": "^3.4", + "php": "8.4.*", + "symfony/console": "^8", + "symfony/filesystem": "^8.0" + }, + "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": "2025-12-30T19:40:28+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "vjik/scaffolder": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/src/changes.php b/src/changes.php index 0dae23d..b04980d 100644 --- a/src/changes.php +++ b/src/changes.php @@ -2,4 +2,13 @@ declare(strict_types=1); -return []; +use Vjik\Scaffolder\Change\NormalizeComposerJson; +use Vjik\Scaffolder\Change\WriteLicense\Bsd3ClauseLicense; +use Vjik\Scaffolder\Change\WriteLicense\WriteLicense; + +return [ + new WriteLicense( + new Bsd3ClauseLicense('Sergei Predvoditelev'), + ), + new NormalizeComposerJson(), +]; From 52b9fd036aec6b458f8ce252abf75ed6994da0a0 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 30 Dec 2025 22:52:54 +0300 Subject: [PATCH 02/30] .editorconfig --- .editorconfig | 17 +++++++++++++++++ LICENSE | 2 +- files/.editorconfig | 17 +++++++++++++++++ files/.gitkeep | 0 src/changes.php | 4 ++++ 5 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 .editorconfig create mode 100644 files/.editorconfig delete mode 100644 files/.gitkeep 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/LICENSE b/LICENSE index 95fbade..31c4d8d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2023-2025 Sergei Predvoditelev. +Copyright (c) 2025 Sergei Predvoditelev. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 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/.gitkeep b/files/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/changes.php b/src/changes.php index b04980d..6493226 100644 --- a/src/changes.php +++ b/src/changes.php @@ -2,13 +2,17 @@ declare(strict_types=1); +use Vjik\Scaffolder\Change\CopyFile; use Vjik\Scaffolder\Change\NormalizeComposerJson; use Vjik\Scaffolder\Change\WriteLicense\Bsd3ClauseLicense; use Vjik\Scaffolder\Change\WriteLicense\WriteLicense; +$files = dirname(__DIR__) . '/files'; + return [ new WriteLicense( new Bsd3ClauseLicense('Sergei Predvoditelev'), ), + new CopyFile($files . '/.editorconfig', '.editorconfig'), new NormalizeComposerJson(), ]; From 5a4b8788d89fece335feb586969c21b402214ef9 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sat, 3 Jan 2026 20:06:40 +0300 Subject: [PATCH 03/30] Add support information to composer.json and update license year --- LICENSE | 2 +- composer.json | 18 +++++++++ composer.lock | 103 +++++++++++++++++++++++++++++++++++++++++------ src/changes.php | 16 +++++++- src/defaults.php | 5 --- src/params.php | 11 +++++ src/run.php | 2 +- 7 files changed, 135 insertions(+), 22 deletions(-) delete mode 100644 src/defaults.php create mode 100644 src/params.php diff --git a/LICENSE b/LICENSE index 31c4d8d..3442016 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2025 Sergei Predvoditelev. +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: diff --git a/composer.json b/composer.json index b031c21..b69066a 100644 --- a/composer.json +++ b/composer.json @@ -1,12 +1,30 @@ { "name": "phptg/scaffolder", + "description": "PHPTG Scaffolder", + "license": "BSD-3-Clause", "type": "project", + "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", "vjik/scaffolder": "dev-master" }, "autoload": { "psr-4": { "Phptg\\Scaffolder\\": "src/" } + }, + "config": { + "bump-after-update": true, + "sort-packages": true } } diff --git a/composer.lock b/composer.lock index c2122e4..9182b6b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c728a750455d10c837150ec90cb36bab", + "content-hash": "68b3554c93448369a98082b740e69019", "packages": [ { "name": "composer/semver", @@ -138,16 +138,16 @@ }, { "name": "symfony/console", - "version": "v8.0.1", + "version": "v8.0.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "fcb73f69d655b48fcb894a262f074218df08bd58" + "reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fcb73f69d655b48fcb894a262f074218df08bd58", - "reference": "fcb73f69d655b48fcb894a262f074218df08bd58", + "url": "https://api.github.com/repos/symfony/console/zipball/6145b304a5c1ea0bdbd0b04d297a5864f9a7d587", + "reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587", "shasum": "" }, "require": { @@ -204,7 +204,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v8.0.1" + "source": "https://github.com/symfony/console/tree/v8.0.3" }, "funding": [ { @@ -224,7 +224,7 @@ "type": "tidelift" } ], - "time": "2025-12-05T15:25:33+00:00" + "time": "2025-12-23T14:52:06+00:00" }, { "name": "symfony/deprecation-contracts", @@ -881,19 +881,24 @@ "source": { "type": "git", "url": "https://github.com/vjik/scaffolder.git", - "reference": "7b7a35f0d3a6798022d3b096e37dc0638b7d0cf6" + "reference": "ce553d28f550cebd897233a3b16ef44290518bd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vjik/scaffolder/zipball/7b7a35f0d3a6798022d3b096e37dc0638b7d0cf6", - "reference": "7b7a35f0d3a6798022d3b096e37dc0638b7d0cf6", + "url": "https://api.github.com/repos/vjik/scaffolder/zipball/ce553d28f550cebd897233a3b16ef44290518bd7", + "reference": "ce553d28f550cebd897233a3b16ef44290518bd7", "shasum": "" }, "require": { "composer/semver": "^3.4", "php": "8.4.*", "symfony/console": "^8", - "symfony/filesystem": "^8.0" + "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", @@ -916,7 +921,77 @@ "issues": "https://github.com/vjik/scaffolder/issues", "source": "https://github.com/vjik/scaffolder/tree/master" }, - "time": "2025-12-30T19:40:28+00:00" + "time": "2026-01-03T17:04:36+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": [], @@ -927,7 +1002,9 @@ }, "prefer-stable": false, "prefer-lowest": false, - "platform": {}, + "platform": { + "php": "^8.4" + }, "platform-dev": {}, "plugin-api-version": "2.9.0" } diff --git a/src/changes.php b/src/changes.php index 6493226..58eb893 100644 --- a/src/changes.php +++ b/src/changes.php @@ -3,16 +3,28 @@ declare(strict_types=1); use Vjik\Scaffolder\Change\CopyFile; -use Vjik\Scaffolder\Change\NormalizeComposerJson; +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; $files = dirname(__DIR__) . '/files'; return [ + new PrepareComposerJson( + prepareAutoloadDev: false, + customChange: static function (array &$composerJson, Context $context): void { + $project = $context->getFact(PackageProject::class); + $composerJson['support'] = [ + 'issues' => "https://github.com/phptg/$project/issues?state=open", + 'chat' => 'https://t.me/predvoditelev_chat', + 'source' => "https://github.com/phptg/$project", + ]; + } + ), new WriteLicense( new Bsd3ClauseLicense('Sergei Predvoditelev'), ), new CopyFile($files . '/.editorconfig', '.editorconfig'), - new NormalizeComposerJson(), ]; 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 @@ - 'BSD-3-Clause', + '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..be58b08 100644 --- a/src/run.php +++ b/src/run.php @@ -9,5 +9,5 @@ new Runner( require __DIR__ . '/changes.php', require __DIR__ . '/facts.php', - require __DIR__ . '/defaults.php', + require __DIR__ . '/params.php', )->run(); From ee2c005b7b9e740ebd0638d908fd3015db2592f8 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sat, 3 Jan 2026 21:14:53 +0300 Subject: [PATCH 04/30] Add rector --- composer.json | 6 ++ composer.lock | 118 +++++++++++++++++++++- files/rector.php | 29 ++++++ rector.php | 28 +++++ src/Change/.gitkeep | 0 src/Change/PrepareRectorConfiguration.php | 61 +++++++++++ src/changes.php | 10 +- 7 files changed, 248 insertions(+), 4 deletions(-) create mode 100644 files/rector.php create mode 100644 rector.php delete mode 100644 src/Change/.gitkeep create mode 100644 src/Change/PrepareRectorConfiguration.php diff --git a/composer.json b/composer.json index b69066a..d4cc3c1 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,9 @@ "php": "^8.4", "vjik/scaffolder": "dev-master" }, + "require-dev": { + "rector/rector": "^2.3" + }, "autoload": { "psr-4": { "Phptg\\Scaffolder\\": "src/" @@ -26,5 +29,8 @@ "config": { "bump-after-update": true, "sort-packages": true + }, + "scripts": { + "rector": "rector" } } diff --git a/composer.lock b/composer.lock index 9182b6b..7089129 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "68b3554c93448369a98082b740e69019", + "content-hash": "0fc0f69a7d6effea9fbd844f9b470cd4", "packages": [ { "name": "composer/semver", @@ -994,7 +994,121 @@ "time": "2025-11-23T18:00:58+00:00" } ], - "packages-dev": [], + "packages-dev": [ + { + "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": { 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/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/src/Change/.gitkeep b/src/Change/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/Change/PrepareRectorConfiguration.php b/src/Change/PrepareRectorConfiguration.php new file mode 100644 index 0000000..08119e2 --- /dev/null +++ b/src/Change/PrepareRectorConfiguration.php @@ -0,0 +1,61 @@ +tryReadFile(self::FILE); + $new = $old ?? file_get_contents(dirname(self::FILE, 2) . '/files/' . self::FILE); + + $phpSet = $this->getPhpSet($context); + if ($phpSet === null) { + return null; + } + + $new = preg_replace( + '/->withPhpSets\([^()]*\)/', + "->withPhpSets($phpSet: true)", + $new, + ); + + if ($old === $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 + { + $constraint = $context->getFact(PhpConstraint::class); + + return array_find_key( + [ + 'php82' => '8.2.9999999', + 'php83' => '8.3.9999999', + 'php84' => '8.4.9999999', + 'php85' => '8.5.9999999', + ], + static fn($version) => $constraint->matches(new Constraint('==', $version)) + ); + } +} diff --git a/src/changes.php b/src/changes.php index 58eb893..f720581 100644 --- a/src/changes.php +++ b/src/changes.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use Phptg\Scaffolder\Change\PrepareRectorConfiguration; use Vjik\Scaffolder\Change\CopyFile; use Vjik\Scaffolder\Change\PrepareComposerJson; use Vjik\Scaffolder\Change\WriteLicense\Bsd3ClauseLicense; @@ -14,17 +15,22 @@ return [ new PrepareComposerJson( prepareAutoloadDev: false, - customChange: static function (array &$composerJson, Context $context): void { + customChange: static function (array &$new, Context $context): void { $project = $context->getFact(PackageProject::class); - $composerJson['support'] = [ + $new['support'] = [ 'issues' => "https://github.com/phptg/$project/issues?state=open", 'chat' => 'https://t.me/predvoditelev_chat', 'source' => "https://github.com/phptg/$project", ]; + + // Rector + $new['require-dev']['rector/rector'] ??= '^2.3.0'; + $new['scripts']['rector'] = 'rector'; } ), new WriteLicense( new Bsd3ClauseLicense('Sergei Predvoditelev'), ), new CopyFile($files . '/.editorconfig', '.editorconfig'), + new PrepareRectorConfiguration(), ]; From 57f8006799fef80954dce5bf6b3a758f733f5353 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sat, 3 Jan 2026 21:57:17 +0300 Subject: [PATCH 05/30] Update --- composer.lock | 8 ++++---- scaffolder.php | 7 +++++++ src/changes.php | 1 - 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 scaffolder.php diff --git a/composer.lock b/composer.lock index 7089129..db56f36 100644 --- a/composer.lock +++ b/composer.lock @@ -881,12 +881,12 @@ "source": { "type": "git", "url": "https://github.com/vjik/scaffolder.git", - "reference": "ce553d28f550cebd897233a3b16ef44290518bd7" + "reference": "3a99674c7a6e2b341f067f655241de38e0ca3b70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vjik/scaffolder/zipball/ce553d28f550cebd897233a3b16ef44290518bd7", - "reference": "ce553d28f550cebd897233a3b16ef44290518bd7", + "url": "https://api.github.com/repos/vjik/scaffolder/zipball/3a99674c7a6e2b341f067f655241de38e0ca3b70", + "reference": "3a99674c7a6e2b341f067f655241de38e0ca3b70", "shasum": "" }, "require": { @@ -921,7 +921,7 @@ "issues": "https://github.com/vjik/scaffolder/issues", "source": "https://github.com/vjik/scaffolder/tree/master" }, - "time": "2026-01-03T17:04:36+00:00" + "time": "2026-01-03T18:52:04+00:00" }, { "name": "yiisoft/strings", diff --git a/scaffolder.php b/scaffolder.php new file mode 100644 index 0000000..6d66649 --- /dev/null +++ b/scaffolder.php @@ -0,0 +1,7 @@ + false, +]; diff --git a/src/changes.php b/src/changes.php index f720581..a56f345 100644 --- a/src/changes.php +++ b/src/changes.php @@ -14,7 +14,6 @@ return [ new PrepareComposerJson( - prepareAutoloadDev: false, customChange: static function (array &$new, Context $context): void { $project = $context->getFact(PackageProject::class); $new['support'] = [ From 5e2c797c3e135fb3213c2ff66728419cb96133fa Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sat, 3 Jan 2026 22:34:50 +0300 Subject: [PATCH 06/30] Add README generation and internals documentation --- README.md | 17 ++++- composer.lock | 8 +- files/docs/internals.md | 1 + files/logo.png | Bin 0 -> 9016 bytes logo.png | Bin 0 -> 9016 bytes scaffolder.php | 3 + src/Change/PrepareReadme.php | 85 ++++++++++++++++++++++ src/Change/PrepareRectorConfiguration.php | 1 - src/changes.php | 6 ++ 9 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 files/docs/internals.md create mode 100644 files/logo.png create mode 100644 logo.png create mode 100644 src/Change/PrepareReadme.php 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.lock b/composer.lock index db56f36..b626f78 100644 --- a/composer.lock +++ b/composer.lock @@ -881,12 +881,12 @@ "source": { "type": "git", "url": "https://github.com/vjik/scaffolder.git", - "reference": "3a99674c7a6e2b341f067f655241de38e0ca3b70" + "reference": "9a4f8797324aed6904b5ff4a77765b650550258c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vjik/scaffolder/zipball/3a99674c7a6e2b341f067f655241de38e0ca3b70", - "reference": "3a99674c7a6e2b341f067f655241de38e0ca3b70", + "url": "https://api.github.com/repos/vjik/scaffolder/zipball/9a4f8797324aed6904b5ff4a77765b650550258c", + "reference": "9a4f8797324aed6904b5ff4a77765b650550258c", "shasum": "" }, "require": { @@ -921,7 +921,7 @@ "issues": "https://github.com/vjik/scaffolder/issues", "source": "https://github.com/vjik/scaffolder/tree/master" }, - "time": "2026-01-03T18:52:04+00:00" + "time": "2026-01-03T19:30:41+00:00" }, { "name": "yiisoft/strings", diff --git a/files/docs/internals.md b/files/docs/internals.md new file mode 100644 index 0000000..949d7ab --- /dev/null +++ b/files/docs/internals.md @@ -0,0 +1 @@ +# Internals diff --git a/files/logo.png b/files/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..cf8943856cebe72cefbf8c9073ac95023d48c33d GIT binary patch literal 9016 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 [ + 'docs-internals', + ], 'prepare-composer-autoload-dev' => false, ]; diff --git a/src/Change/PrepareReadme.php b/src/Change/PrepareReadme.php new file mode 100644 index 0000000..893b36d --- /dev/null +++ b/src/Change/PrepareReadme.php @@ -0,0 +1,85 @@ +tryReadFile(self::FILE); + $new = $old ?? $this->createNew($context); + + if ($old === $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 = $context->getFact(PhpConstraint::class)->getPrettyString(); + + 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 + + - PHP $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; + } +} diff --git a/src/Change/PrepareRectorConfiguration.php b/src/Change/PrepareRectorConfiguration.php index 08119e2..d7f71b1 100644 --- a/src/Change/PrepareRectorConfiguration.php +++ b/src/Change/PrepareRectorConfiguration.php @@ -37,7 +37,6 @@ public function decide(Context $context): callable|array|null return null; } - return static fn(Cli $cli) => $cli->step( sprintf('Write `%s`', self::FILE), fn() => $context->writeTextFile(self::FILE, $new), diff --git a/src/changes.php b/src/changes.php index a56f345..249e9ad 100644 --- a/src/changes.php +++ b/src/changes.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use Phptg\Scaffolder\Change\PrepareReadme; use Phptg\Scaffolder\Change\PrepareRectorConfiguration; use Vjik\Scaffolder\Change\CopyFile; use Vjik\Scaffolder\Change\PrepareComposerJson; @@ -32,4 +33,9 @@ ), new CopyFile($files . '/.editorconfig', '.editorconfig'), new PrepareRectorConfiguration(), + 'readme' => [ + new PrepareReadme(), + new CopyFile($files . '/logo.png', 'logo.png'), + ], + 'docs-internals' => new CopyFile($files . '/docs/internals.md', 'docs/internals.md'), ]; From 69c7ae5a00b7836bfb29270fdd2f9b1b127fdfc4 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sat, 3 Jan 2026 22:52:48 +0300 Subject: [PATCH 07/30] Refactor PHP version handling in PrepareRectorConfiguration --- composer.lock | 8 ++++---- src/Change/PrepareRectorConfiguration.php | 24 +++++++++++------------ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/composer.lock b/composer.lock index b626f78..1a8d85d 100644 --- a/composer.lock +++ b/composer.lock @@ -881,12 +881,12 @@ "source": { "type": "git", "url": "https://github.com/vjik/scaffolder.git", - "reference": "9a4f8797324aed6904b5ff4a77765b650550258c" + "reference": "cc99be57b31b267c6a3b1a682f311a298547ec50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vjik/scaffolder/zipball/9a4f8797324aed6904b5ff4a77765b650550258c", - "reference": "9a4f8797324aed6904b5ff4a77765b650550258c", + "url": "https://api.github.com/repos/vjik/scaffolder/zipball/cc99be57b31b267c6a3b1a682f311a298547ec50", + "reference": "cc99be57b31b267c6a3b1a682f311a298547ec50", "shasum": "" }, "require": { @@ -921,7 +921,7 @@ "issues": "https://github.com/vjik/scaffolder/issues", "source": "https://github.com/vjik/scaffolder/tree/master" }, - "time": "2026-01-03T19:30:41+00:00" + "time": "2026-01-03T19:49:32+00:00" }, { "name": "yiisoft/strings", diff --git a/src/Change/PrepareRectorConfiguration.php b/src/Change/PrepareRectorConfiguration.php index d7f71b1..88fedc9 100644 --- a/src/Change/PrepareRectorConfiguration.php +++ b/src/Change/PrepareRectorConfiguration.php @@ -4,11 +4,11 @@ namespace Phptg\Scaffolder\Change; -use Composer\Semver\Constraint\Constraint; use Vjik\Scaffolder\Change; use Vjik\Scaffolder\Cli; use Vjik\Scaffolder\Context; -use Vjik\Scaffolder\Fact\PhpConstraint; +use Vjik\Scaffolder\Fact\LowestMinorPhpVersion; +use Vjik\Scaffolder\Value\MinorPhpVersion; use function dirname; use function sprintf; @@ -45,16 +45,14 @@ public function decide(Context $context): callable|array|null private function getPhpSet(Context $context): ?string { - $constraint = $context->getFact(PhpConstraint::class); - - return array_find_key( - [ - 'php82' => '8.2.9999999', - 'php83' => '8.3.9999999', - 'php84' => '8.4.9999999', - 'php85' => '8.5.9999999', - ], - static fn($version) => $constraint->matches(new Constraint('==', $version)) - ); + $phpVersion = $context->getFact(LowestMinorPhpVersion::class); + + return match ($phpVersion) { + MinorPhpVersion::PHP82 => 'php82', + MinorPhpVersion::PHP83 => 'php83', + MinorPhpVersion::PHP84 => 'php84', + MinorPhpVersion::PHP85 => 'php85', + default => null, + }; } } From 6db1ec44f067dcc173a5124b610881db12672ca7 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sat, 3 Jan 2026 23:16:59 +0300 Subject: [PATCH 08/30] phpunit --- .gitignore | 1 + files/phpunit.xml.dist | 31 +++++++++++++++++++++++ files/runtime/.gitignore | 2 ++ runtime/.gitignore | 2 ++ scaffolder.php | 2 ++ src/Change/PrepareGitignore.php | 44 +++++++++++++++++++++++++++++++++ src/changes.php | 10 ++++++++ 7 files changed, 92 insertions(+) create mode 100644 files/phpunit.xml.dist create mode 100644 files/runtime/.gitignore create mode 100644 runtime/.gitignore create mode 100644 src/Change/PrepareGitignore.php diff --git a/.gitignore b/.gitignore index 57872d0..c93151d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +# Composer /vendor/ 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/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/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 index c4bd410..a12f8fe 100644 --- a/scaffolder.php +++ b/scaffolder.php @@ -5,6 +5,8 @@ return [ 'disable' => [ 'docs-internals', + 'phpunit-configuration', ], 'prepare-composer-autoload-dev' => false, + 'phpunit' => false, ]; diff --git a/src/Change/PrepareGitignore.php b/src/Change/PrepareGitignore.php new file mode 100644 index 0000000..e0d68a3 --- /dev/null +++ b/src/Change/PrepareGitignore.php @@ -0,0 +1,44 @@ +tryReadFile(self::FILE); + $new = $old ?? $this->createNew($context); + + if ($old === $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 + { + $composerLock = $context->getFact(PackageType::class) === 'library' ? "\n/composer.lock" : ''; + return <<getParam('phpunit', true)) { + $new['require-dev']['phpunit/phpunit'] ??= '^11.5.46'; + } } ), + new PrepareGitignore(), + new CopyFile($files . '/runtime/.gitignore', 'runtime/.gitignore'), new WriteLicense( new Bsd3ClauseLicense('Sergei Predvoditelev'), ), @@ -38,4 +47,5 @@ new CopyFile($files . '/logo.png', 'logo.png'), ], 'docs-internals' => new CopyFile($files . '/docs/internals.md', 'docs/internals.md'), + 'phpunit-configuration' => new CopyFileIfNotExists($files . '/phpunit.xml.dist', 'phpunit.xml.dist') ]; From 4d2078e5e8ddf5c101ab6340634a87ce9ea6715d Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 11:32:46 +0300 Subject: [PATCH 09/30] Enhance README generation with dynamic PHP constraint handling --- composer.lock | 8 ++++---- src/Change/PrepareReadme.php | 28 +++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index 1a8d85d..181775f 100644 --- a/composer.lock +++ b/composer.lock @@ -881,12 +881,12 @@ "source": { "type": "git", "url": "https://github.com/vjik/scaffolder.git", - "reference": "cc99be57b31b267c6a3b1a682f311a298547ec50" + "reference": "ae712e03e9149286d6290491a71ed4eee1e75b7b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vjik/scaffolder/zipball/cc99be57b31b267c6a3b1a682f311a298547ec50", - "reference": "cc99be57b31b267c6a3b1a682f311a298547ec50", + "url": "https://api.github.com/repos/vjik/scaffolder/zipball/ae712e03e9149286d6290491a71ed4eee1e75b7b", + "reference": "ae712e03e9149286d6290491a71ed4eee1e75b7b", "shasum": "" }, "require": { @@ -921,7 +921,7 @@ "issues": "https://github.com/vjik/scaffolder/issues", "source": "https://github.com/vjik/scaffolder/tree/master" }, - "time": "2026-01-03T19:49:32+00:00" + "time": "2026-01-04T08:26:45+00:00" }, { "name": "yiisoft/strings", diff --git a/src/Change/PrepareReadme.php b/src/Change/PrepareReadme.php index 893b36d..89df535 100644 --- a/src/Change/PrepareReadme.php +++ b/src/Change/PrepareReadme.php @@ -7,9 +7,12 @@ use Vjik\Scaffolder\Change; use Vjik\Scaffolder\Cli; use Vjik\Scaffolder\Context; +use Vjik\Scaffolder\Fact\HighestMinorPhpVersion; +use Vjik\Scaffolder\Fact\LowestMinorPhpVersion; use Vjik\Scaffolder\Fact\PackageProject; -use Vjik\Scaffolder\Fact\PhpConstraint; +use Vjik\Scaffolder\Fact\PhpConstraintName; use Vjik\Scaffolder\Fact\Title; +use Vjik\Scaffolder\Value\MinorPhpVersion; use function sprintf; @@ -36,7 +39,7 @@ private function createNew(Context $context): string { $title = $context->getFact(Title::class); $packageProject = $context->getFact(PackageProject::class); - $phpConstraint = $context->getFact(PhpConstraint::class)->getPrettyString(); + $phpConstraint = $this->createPhpConstraint($context); return << @@ -56,7 +59,7 @@ private function createNew(Context $context): string ## Requirements - - PHP $phpConstraint. + - $phpConstraint. ## Installation @@ -82,4 +85,23 @@ private function createNew(Context $context): string 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; + } } From 6ada3924fb2c6320da75d4c7bf3f1f2c6c0d19bc Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 12:05:30 +0300 Subject: [PATCH 10/30] Add Psalm support --- composer.json | 11 ++++++ composer.lock | 67 ++++++++++++++++++++++++++++++--- files/psalm.xml | 20 ++++++++++ files/tools/.gitignore | 2 + files/tools/psalm/composer.json | 5 +++ scaffolder.php | 1 + src/Fact/.gitkeep | 0 src/Fact/UsePsalm.php | 35 +++++++++++++++++ src/changes.php | 26 ++++++++++++- src/facts.php | 6 ++- tools/.gitignore | 2 + 11 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 files/psalm.xml create mode 100644 files/tools/.gitignore create mode 100644 files/tools/psalm/composer.json delete mode 100644 src/Fact/.gitkeep create mode 100644 src/Fact/UsePsalm.php create mode 100644 tools/.gitignore diff --git a/composer.json b/composer.json index d4cc3c1..229b676 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "vjik/scaffolder": "dev-master" }, "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.3", "rector/rector": "^2.3" }, "autoload": { @@ -27,9 +28,19 @@ } }, "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": { "rector": "rector" } diff --git a/composer.lock b/composer.lock index 181775f..22a9e12 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0fc0f69a7d6effea9fbd844f9b470cd4", + "content-hash": "ddc9f1c869f333a0d23a42df50694d54", "packages": [ { "name": "composer/semver", @@ -881,12 +881,12 @@ "source": { "type": "git", "url": "https://github.com/vjik/scaffolder.git", - "reference": "ae712e03e9149286d6290491a71ed4eee1e75b7b" + "reference": "8b1088399dda5039d7503f0202b332313af7e190" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vjik/scaffolder/zipball/ae712e03e9149286d6290491a71ed4eee1e75b7b", - "reference": "ae712e03e9149286d6290491a71ed4eee1e75b7b", + "url": "https://api.github.com/repos/vjik/scaffolder/zipball/8b1088399dda5039d7503f0202b332313af7e190", + "reference": "8b1088399dda5039d7503f0202b332313af7e190", "shasum": "" }, "require": { @@ -921,7 +921,7 @@ "issues": "https://github.com/vjik/scaffolder/issues", "source": "https://github.com/vjik/scaffolder/tree/master" }, - "time": "2026-01-04T08:26:45+00:00" + "time": "2026-01-04T08:58:30+00:00" }, { "name": "yiisoft/strings", @@ -995,6 +995,63 @@ } ], "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", 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/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/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/scaffolder.php b/scaffolder.php index a12f8fe..7cd11d1 100644 --- a/scaffolder.php +++ b/scaffolder.php @@ -9,4 +9,5 @@ ], 'prepare-composer-autoload-dev' => false, 'phpunit' => false, + 'use-psalm' => false, ]; diff --git a/src/Fact/.gitkeep b/src/Fact/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/Fact/UsePsalm.php b/src/Fact/UsePsalm.php new file mode 100644 index 0000000..bbf9bc9 --- /dev/null +++ b/src/Fact/UsePsalm.php @@ -0,0 +1,35 @@ + + */ +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 4c4132c..93558fd 100644 --- a/src/changes.php +++ b/src/changes.php @@ -5,6 +5,8 @@ use Phptg\Scaffolder\Change\PrepareGitignore; use Phptg\Scaffolder\Change\PrepareReadme; use Phptg\Scaffolder\Change\PrepareRectorConfiguration; +use Phptg\Scaffolder\Fact\UsePsalm; +use Vjik\Scaffolder\Change\ChangeIf; use Vjik\Scaffolder\Change\CopyFile; use Vjik\Scaffolder\Change\CopyFileIfNotExists; use Vjik\Scaffolder\Change\PrepareComposerJson; @@ -25,10 +27,24 @@ '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'; + // Psalm + if ($context->getFact(UsePsalm::class)) { + $new['scripts']['psalm'] = 'psalm'; + } + // PHPUnit if ($context->getParam('phpunit', true)) { $new['require-dev']['phpunit/phpunit'] ??= '^11.5.46'; @@ -47,5 +63,13 @@ new CopyFile($files . '/logo.png', 'logo.png'), ], 'docs-internals' => new CopyFile($files . '/docs/internals.md', 'docs/internals.md'), - 'phpunit-configuration' => new CopyFileIfNotExists($files . '/phpunit.xml.dist', 'phpunit.xml.dist') + 'phpunit-configuration' => new CopyFileIfNotExists($files . '/phpunit.xml.dist', 'phpunit.xml.dist'), + new CopyFile($files . '/tools/.gitignore', 'tools/.gitignore'), + new ChangeIf( + [ + new CopyFileIfNotExists($files . '/tools/psalm/composer.json', 'tools/psalm/composer.json'), + new CopyFileIfNotExists($files . '/psalm.xml', 'psalm.xml'), + ], + UsePsalm::class, + ), ]; diff --git a/src/facts.php b/src/facts.php index 0dae23d..d42b09a 100644 --- a/src/facts.php +++ b/src/facts.php @@ -2,4 +2,8 @@ declare(strict_types=1); -return []; +use Phptg\Scaffolder\Fact; + +return [ + Fact\UsePsalm::class, +]; 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 From abba9562e062e39d48153fa72db5f42b4aa75ab1 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 12:10:37 +0300 Subject: [PATCH 11/30] Add UsePhpUnit fact and update gitignore handling for PHPUnit --- scaffolder.php | 3 +-- src/Change/PrepareGitignore.php | 25 ++++++++++++++++-------- src/Fact/UsePhpUnit.php | 34 +++++++++++++++++++++++++++++++++ src/changes.php | 8 ++++++-- src/facts.php | 1 + 5 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 src/Fact/UsePhpUnit.php diff --git a/scaffolder.php b/scaffolder.php index 7cd11d1..dabc898 100644 --- a/scaffolder.php +++ b/scaffolder.php @@ -5,9 +5,8 @@ return [ 'disable' => [ 'docs-internals', - 'phpunit-configuration', ], 'prepare-composer-autoload-dev' => false, - 'phpunit' => false, + 'use-phpunit' => false, 'use-psalm' => false, ]; diff --git a/src/Change/PrepareGitignore.php b/src/Change/PrepareGitignore.php index e0d68a3..4dd72e6 100644 --- a/src/Change/PrepareGitignore.php +++ b/src/Change/PrepareGitignore.php @@ -4,6 +4,7 @@ namespace Phptg\Scaffolder\Change; +use Phptg\Scaffolder\Fact\UsePhpUnit; use Vjik\Scaffolder\Change; use Vjik\Scaffolder\Cli; use Vjik\Scaffolder\Context; @@ -32,13 +33,21 @@ public function decide(Context $context): callable|array|null private function createNew(Context $context): string { - $composerLock = $context->getFact(PackageType::class) === 'library' ? "\n/composer.lock" : ''; - return <<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/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/changes.php b/src/changes.php index 93558fd..f417f93 100644 --- a/src/changes.php +++ b/src/changes.php @@ -5,6 +5,7 @@ use Phptg\Scaffolder\Change\PrepareGitignore; use Phptg\Scaffolder\Change\PrepareReadme; use Phptg\Scaffolder\Change\PrepareRectorConfiguration; +use Phptg\Scaffolder\Fact\UsePhpUnit; use Phptg\Scaffolder\Fact\UsePsalm; use Vjik\Scaffolder\Change\ChangeIf; use Vjik\Scaffolder\Change\CopyFile; @@ -46,7 +47,7 @@ } // PHPUnit - if ($context->getParam('phpunit', true)) { + if ($context->getFact(UsePhpUnit::class)) { $new['require-dev']['phpunit/phpunit'] ??= '^11.5.46'; } } @@ -63,8 +64,11 @@ new CopyFile($files . '/logo.png', 'logo.png'), ], 'docs-internals' => new CopyFile($files . '/docs/internals.md', 'docs/internals.md'), - 'phpunit-configuration' => new CopyFileIfNotExists($files . '/phpunit.xml.dist', 'phpunit.xml.dist'), new CopyFile($files . '/tools/.gitignore', 'tools/.gitignore'), + new ChangeIf( + new CopyFileIfNotExists($files . '/phpunit.xml.dist', 'phpunit.xml.dist'), + UsePhpUnit::class, + ), new ChangeIf( [ new CopyFileIfNotExists($files . '/tools/psalm/composer.json', 'tools/psalm/composer.json'), diff --git a/src/facts.php b/src/facts.php index d42b09a..4301722 100644 --- a/src/facts.php +++ b/src/facts.php @@ -5,5 +5,6 @@ use Phptg\Scaffolder\Fact; return [ + Fact\UsePhpUnit::class, Fact\UsePsalm::class, ]; From 3fd54c13496359c8b4904cd07d3e574384fcd6c8 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 12:25:38 +0300 Subject: [PATCH 12/30] Add PHPStan support and configuration --- composer.json | 2 ++ composer.lock | 10 +++---- files/phpstan.neon | 9 ++++++ phpstan.neon | 9 ++++++ scaffolder.php | 1 + src/Change/PrepareRectorConfiguration.php | 3 ++ src/Fact/UsePhpStan.php | 34 +++++++++++++++++++++++ src/Fact/UsePsalm.php | 1 - src/changes.php | 15 +++++++++- src/facts.php | 1 + src/run.php | 17 ++++++++---- 11 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 files/phpstan.neon create mode 100644 phpstan.neon create mode 100644 src/Fact/UsePhpStan.php diff --git a/composer.json b/composer.json index 229b676..fce0f96 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.3", + "phpstan/phpstan": "^2.1.33", "rector/rector": "^2.3" }, "autoload": { @@ -42,6 +43,7 @@ } }, "scripts": { + "phpstan": "phpstan analyse -c phpstan.neon", "rector": "rector" } } diff --git a/composer.lock b/composer.lock index 22a9e12..dfce54a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ddc9f1c869f333a0d23a42df50694d54", + "content-hash": "1a867347dfe573e8e79ad96ccb682876", "packages": [ { "name": "composer/semver", @@ -881,12 +881,12 @@ "source": { "type": "git", "url": "https://github.com/vjik/scaffolder.git", - "reference": "8b1088399dda5039d7503f0202b332313af7e190" + "reference": "150b69c109a760fb21e9bfc371ff7bbbf7f841e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vjik/scaffolder/zipball/8b1088399dda5039d7503f0202b332313af7e190", - "reference": "8b1088399dda5039d7503f0202b332313af7e190", + "url": "https://api.github.com/repos/vjik/scaffolder/zipball/150b69c109a760fb21e9bfc371ff7bbbf7f841e1", + "reference": "150b69c109a760fb21e9bfc371ff7bbbf7f841e1", "shasum": "" }, "require": { @@ -921,7 +921,7 @@ "issues": "https://github.com/vjik/scaffolder/issues", "source": "https://github.com/vjik/scaffolder/tree/master" }, - "time": "2026-01-04T08:58:30+00:00" + "time": "2026-01-04T09:23:19+00:00" }, { "name": "yiisoft/strings", 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/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/scaffolder.php b/scaffolder.php index dabc898..7e3900f 100644 --- a/scaffolder.php +++ b/scaffolder.php @@ -9,4 +9,5 @@ 'prepare-composer-autoload-dev' => false, 'use-phpunit' => false, 'use-psalm' => false, + 'use-phpstan' => true, ]; diff --git a/src/Change/PrepareRectorConfiguration.php b/src/Change/PrepareRectorConfiguration.php index 88fedc9..ea3cbf6 100644 --- a/src/Change/PrepareRectorConfiguration.php +++ b/src/Change/PrepareRectorConfiguration.php @@ -20,6 +20,8 @@ public function decide(Context $context): callable|array|null { $old = $context->tryReadFile(self::FILE); + + /** @var string $new */ $new = $old ?? file_get_contents(dirname(self::FILE, 2) . '/files/' . self::FILE); $phpSet = $this->getPhpSet($context); @@ -27,6 +29,7 @@ public function decide(Context $context): callable|array|null return null; } + /** @var string $new */ $new = preg_replace( '/->withPhpSets\([^()]*\)/', "->withPhpSets($phpSet: true)", 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/UsePsalm.php b/src/Fact/UsePsalm.php index bbf9bc9..e2cdfd5 100644 --- a/src/Fact/UsePsalm.php +++ b/src/Fact/UsePsalm.php @@ -31,5 +31,4 @@ 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 f417f93..338a6b4 100644 --- a/src/changes.php +++ b/src/changes.php @@ -5,6 +5,7 @@ use Phptg\Scaffolder\Change\PrepareGitignore; use Phptg\Scaffolder\Change\PrepareReadme; use Phptg\Scaffolder\Change\PrepareRectorConfiguration; +use Phptg\Scaffolder\Fact\UsePhpStan; use Phptg\Scaffolder\Fact\UsePhpUnit; use Phptg\Scaffolder\Fact\UsePsalm; use Vjik\Scaffolder\Change\ChangeIf; @@ -20,7 +21,7 @@ return [ new PrepareComposerJson( - customChange: static function (array &$new, Context $context): void { + customChange: static function (array $new, Context $context): array { $project = $context->getFact(PackageProject::class); $new['support'] = [ 'issues' => "https://github.com/phptg/$project/issues?state=open", @@ -46,10 +47,18 @@ $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'; } + + return $new; } ), new PrepareGitignore(), @@ -76,4 +85,8 @@ ], UsePsalm::class, ), + new ChangeIf( + new CopyFileIfNotExists($files . '/phpstan.neon', 'phpstan.neon'), + UsePhpStan::class, + ), ]; diff --git a/src/facts.php b/src/facts.php index 4301722..d9c1aec 100644 --- a/src/facts.php +++ b/src/facts.php @@ -5,6 +5,7 @@ use Phptg\Scaffolder\Fact; return [ + Fact\UsePhpStan::class, Fact\UsePhpUnit::class, Fact\UsePsalm::class, ]; diff --git a/src/run.php b/src/run.php index be58b08..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__ . '/params.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(); From ddcd6e6bdebf53c2edd3e9d6361182f1adee8543 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 12:31:49 +0300 Subject: [PATCH 13/30] Add PHP CS Fixer support and configuration --- .php-cs-fixer.dist.php | 20 +++++++++++++++ composer.json | 1 + files/.php-cs-fixer.dist.php | 21 ++++++++++++++++ files/tools/php-cs-fixer/composer.json | 5 ++++ src/Fact/UsePhpCsFixer.php | 34 ++++++++++++++++++++++++++ src/changes.php | 15 +++++++++++- src/facts.php | 1 + tools/php-cs-fixer/composer.json | 5 ++++ 8 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 .php-cs-fixer.dist.php create mode 100644 files/.php-cs-fixer.dist.php create mode 100644 files/tools/php-cs-fixer/composer.json create mode 100644 src/Fact/UsePhpCsFixer.php create mode 100644 tools/php-cs-fixer/composer.json 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/composer.json b/composer.json index fce0f96..8960e30 100644 --- a/composer.json +++ b/composer.json @@ -43,6 +43,7 @@ } }, "scripts": { + "cs-fix": "php-cs-fixer fix", "phpstan": "phpstan analyse -c phpstan.neon", "rector": "rector" } 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/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/src/Fact/UsePhpCsFixer.php b/src/Fact/UsePhpCsFixer.php new file mode 100644 index 0000000..d025960 --- /dev/null +++ b/src/Fact/UsePhpCsFixer.php @@ -0,0 +1,34 @@ + + */ +final class UsePhpCsFixer extends Fact +{ + public const string VALUE_OPTION = 'use-phpcsfixer'; + + 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 338a6b4..51f9a91 100644 --- a/src/changes.php +++ b/src/changes.php @@ -5,6 +5,7 @@ use Phptg\Scaffolder\Change\PrepareGitignore; use Phptg\Scaffolder\Change\PrepareReadme; use Phptg\Scaffolder\Change\PrepareRectorConfiguration; +use Phptg\Scaffolder\Fact\UsePhpCsFixer; use Phptg\Scaffolder\Fact\UsePhpStan; use Phptg\Scaffolder\Fact\UsePhpUnit; use Phptg\Scaffolder\Fact\UsePsalm; @@ -58,8 +59,13 @@ $new['require-dev']['phpunit/phpunit'] ??= '^11.5.46'; } + // PHP CS Fixer + if ($context->getFact(UsePhpCsFixer::class)) { + $new['scripts']['cs-fix'] ??= 'php-cs-fixer fix'; + } + return $new; - } + }, ), new PrepareGitignore(), new CopyFile($files . '/runtime/.gitignore', 'runtime/.gitignore'), @@ -89,4 +95,11 @@ new CopyFileIfNotExists($files . '/phpstan.neon', 'phpstan.neon'), UsePhpStan::class, ), + new ChangeIf( + [ + 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'), + ], + UsePhpCsFixer::class, + ), ]; diff --git a/src/facts.php b/src/facts.php index d9c1aec..1e284c0 100644 --- a/src/facts.php +++ b/src/facts.php @@ -5,6 +5,7 @@ use Phptg\Scaffolder\Fact; return [ + Fact\UsePhpCsFixer::class, Fact\UsePhpStan::class, Fact\UsePhpUnit::class, Fact\UsePsalm::class, 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" + } +} From 9d5341cec02b90c35efd122e979bc0c92f4fc474 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 12:39:48 +0300 Subject: [PATCH 14/30] Add Composer Dependency Analyser support and configuration --- composer-dependency-analyser.php | 11 ++++++ composer.json | 3 ++ composer.lock | 5 +-- files/composer-dependency-analyser.php | 12 +++++++ .../composer.json | 5 +++ src/Fact/UseComposerDependencyAnalyser.php | 34 +++++++++++++++++++ src/changes.php | 13 +++++++ src/facts.php | 1 + .../composer.json | 5 +++ 9 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 composer-dependency-analyser.php create mode 100644 files/composer-dependency-analyser.php create mode 100644 files/tools/composer-dependency-analyser/composer.json create mode 100644 src/Fact/UseComposerDependencyAnalyser.php create mode 100644 tools/composer-dependency-analyser/composer.json 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 8960e30..908a317 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,8 @@ }, "require": { "php": "^8.4", + "ext-filter": "*", + "symfony/console": "^8.0.3", "vjik/scaffolder": "dev-master" }, "require-dev": { @@ -44,6 +46,7 @@ }, "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 index dfce54a..b60abf6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1a867347dfe573e8e79ad96ccb682876", + "content-hash": "bb5a146140ee14dda2d09b93517e34bb", "packages": [ { "name": "composer/semver", @@ -1174,7 +1174,8 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^8.4" + "php": "^8.4", + "ext-filter": "*" }, "platform-dev": {}, "plugin-api-version": "2.9.0" 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/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/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/changes.php b/src/changes.php index 51f9a91..714e7af 100644 --- a/src/changes.php +++ b/src/changes.php @@ -5,6 +5,7 @@ use Phptg\Scaffolder\Change\PrepareGitignore; use Phptg\Scaffolder\Change\PrepareReadme; use Phptg\Scaffolder\Change\PrepareRectorConfiguration; +use Phptg\Scaffolder\Fact\UseComposerDependencyAnalyser; use Phptg\Scaffolder\Fact\UsePhpCsFixer; use Phptg\Scaffolder\Fact\UsePhpStan; use Phptg\Scaffolder\Fact\UsePhpUnit; @@ -64,6 +65,11 @@ $new['scripts']['cs-fix'] ??= 'php-cs-fixer fix'; } + // Composer Dependency Analyser + if ($context->getFact(UseComposerDependencyAnalyser::class)) { + $new['scripts']['dependency-analyser'] ??= 'composer-dependency-analyser'; + } + return $new; }, ), @@ -102,4 +108,11 @@ ], UsePhpCsFixer::class, ), + 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'), + ], + UseComposerDependencyAnalyser::class, + ), ]; diff --git a/src/facts.php b/src/facts.php index 1e284c0..49027ab 100644 --- a/src/facts.php +++ b/src/facts.php @@ -5,6 +5,7 @@ use Phptg\Scaffolder\Fact; return [ + Fact\UseComposerDependencyAnalyser::class, Fact\UsePhpCsFixer::class, Fact\UsePhpStan::class, Fact\UsePhpUnit::class, 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" + } +} From 0652e1256c1a95e706866da26a86a08fe8ffb81d Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 13:09:55 +0300 Subject: [PATCH 15/30] Add support for EnsureDirectoryWithGitkeep and update package parameters --- composer.lock | 8 ++++---- src/changes.php | 5 +++++ src/params.php | 2 ++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index b60abf6..48cdc4d 100644 --- a/composer.lock +++ b/composer.lock @@ -881,12 +881,12 @@ "source": { "type": "git", "url": "https://github.com/vjik/scaffolder.git", - "reference": "150b69c109a760fb21e9bfc371ff7bbbf7f841e1" + "reference": "899f01b6db77ebaa069076c92de155a7b3383352" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vjik/scaffolder/zipball/150b69c109a760fb21e9bfc371ff7bbbf7f841e1", - "reference": "150b69c109a760fb21e9bfc371ff7bbbf7f841e1", + "url": "https://api.github.com/repos/vjik/scaffolder/zipball/899f01b6db77ebaa069076c92de155a7b3383352", + "reference": "899f01b6db77ebaa069076c92de155a7b3383352", "shasum": "" }, "require": { @@ -921,7 +921,7 @@ "issues": "https://github.com/vjik/scaffolder/issues", "source": "https://github.com/vjik/scaffolder/tree/master" }, - "time": "2026-01-04T09:23:19+00:00" + "time": "2026-01-04T10:08:33+00:00" }, { "name": "yiisoft/strings", diff --git a/src/changes.php b/src/changes.php index 714e7af..d589a12 100644 --- a/src/changes.php +++ b/src/changes.php @@ -13,11 +13,14 @@ 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'; @@ -73,6 +76,8 @@ return $new; }, ), + new EnsureDirectoryWithGitkeep(SourceDirectory::class), + new EnsureDirectoryWithGitkeep(TestsDirectory::class), new PrepareGitignore(), new CopyFile($files . '/runtime/.gitignore', 'runtime/.gitignore'), new WriteLicense( diff --git a/src/params.php b/src/params.php index b559b20..188e260 100644 --- a/src/params.php +++ b/src/params.php @@ -3,7 +3,9 @@ declare(strict_types=1); return [ + 'package-vendor' => '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/', From e6872ee633bc1c92d72986d94f3c6a1d686f3883 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 13:12:22 +0300 Subject: [PATCH 16/30] Add .gitattributes file for text file handling and export-ignore rules --- files/.gitattributes | 36 ++++++++++++++++++++++++++++++++++++ src/changes.php | 1 + 2 files changed, 37 insertions(+) create mode 100644 files/.gitattributes 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/src/changes.php b/src/changes.php index d589a12..a219a50 100644 --- a/src/changes.php +++ b/src/changes.php @@ -120,4 +120,5 @@ ], UseComposerDependencyAnalyser::class, ), + new CopyFileIfNotExists($files . '/.gitattributes', '.gitattributes'), ]; From d688c00d983de821ec16bd2faa1dc02d7d328b4e Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 13:15:37 +0300 Subject: [PATCH 17/30] Add PrepareChangelog class to generate and manage CHANGELOG.md --- src/Change/PrepareChangelog.php | 45 +++++++++++++++++++++++++++++++++ src/changes.php | 2 ++ 2 files changed, 47 insertions(+) create mode 100644 src/Change/PrepareChangelog.php diff --git a/src/Change/PrepareChangelog.php b/src/Change/PrepareChangelog.php new file mode 100644 index 0000000..752beb8 --- /dev/null +++ b/src/Change/PrepareChangelog.php @@ -0,0 +1,45 @@ +tryReadFile(self::FILE); + $new = $old ?? $this->createNew($context); + + if ($old === $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 << new CopyFile($files . '/docs/internals.md', 'docs/internals.md'), + new PrepareChangelog(), new CopyFile($files . '/tools/.gitignore', 'tools/.gitignore'), new ChangeIf( new CopyFileIfNotExists($files . '/phpunit.xml.dist', 'phpunit.xml.dist'), From 85de9a8a75f708e4eff545c951c172bae784f3b5 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 13:20:26 +0300 Subject: [PATCH 18/30] Add .gitattributes and CHANGELOG.md --- .gitattributes | 36 ++++++++++++++++++++++++++++++++++++ CHANGELOG.md | 5 +++++ scaffolder.php | 2 ++ src/changes.php | 2 +- 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 .gitattributes create mode 100644 CHANGELOG.md 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/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/scaffolder.php b/scaffolder.php index 7e3900f..1a8b372 100644 --- a/scaffolder.php +++ b/scaffolder.php @@ -3,8 +3,10 @@ declare(strict_types=1); return [ + 'title' => 'PHPTG Scaffolder', 'disable' => [ 'docs-internals', + 'tests-directory', ], 'prepare-composer-autoload-dev' => false, 'use-phpunit' => false, diff --git a/src/changes.php b/src/changes.php index a983e0b..a0e916e 100644 --- a/src/changes.php +++ b/src/changes.php @@ -78,7 +78,7 @@ }, ), new EnsureDirectoryWithGitkeep(SourceDirectory::class), - new EnsureDirectoryWithGitkeep(TestsDirectory::class), + 'tests-directory' => new EnsureDirectoryWithGitkeep(TestsDirectory::class), new PrepareGitignore(), new CopyFile($files . '/runtime/.gitignore', 'runtime/.gitignore'), new WriteLicense( From b94b5741f9322421e9a889fea567c0663ce8f12f Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 13:30:22 +0300 Subject: [PATCH 19/30] Add PrepareDocsInternals class to manage and generate documentation for internals --- files/docs/internals.md | 1 - src/Change/PrepareDocsInternals.php | 107 ++++++++++++++++++++++++++++ src/changes.php | 3 +- 3 files changed, 109 insertions(+), 2 deletions(-) delete mode 100644 files/docs/internals.md create mode 100644 src/Change/PrepareDocsInternals.php diff --git a/files/docs/internals.md b/files/docs/internals.md deleted file mode 100644 index 949d7ab..0000000 --- a/files/docs/internals.md +++ /dev/null @@ -1 +0,0 @@ -# Internals diff --git a/src/Change/PrepareDocsInternals.php b/src/Change/PrepareDocsInternals.php new file mode 100644 index 0000000..99bb445 --- /dev/null +++ b/src/Change/PrepareDocsInternals.php @@ -0,0 +1,107 @@ +tryReadFile(self::FILE); + $new = $old ?? $this->createNew($context); + + if ($old === $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(UsePhpCsFixer::class)) { + $blocks[] = <<getFact(UseComposerDependencyAnalyser::class)) { + $blocks[] = << new CopyFile($files . '/docs/internals.md', 'docs/internals.md'), + 'docs-internals' => new PrepareDocsInternals(), new PrepareChangelog(), new CopyFile($files . '/tools/.gitignore', 'tools/.gitignore'), new ChangeIf( From 0012c2c57fed8adb3b66bc4d21f78f71e1ab3d9a Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 13:35:12 +0300 Subject: [PATCH 20/30] Add Infection support with configuration files and command integration --- files/infection.json.dist | 16 ++++++++++++++ files/tools/infection/composer.json | 10 +++++++++ scaffolder.php | 1 + src/Change/PrepareDocsInternals.php | 1 - src/Fact/UseInfection.php | 34 +++++++++++++++++++++++++++++ src/changes.php | 13 +++++++++++ src/facts.php | 1 + 7 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 files/infection.json.dist create mode 100644 files/tools/infection/composer.json create mode 100644 src/Fact/UseInfection.php 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/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/scaffolder.php b/scaffolder.php index 1a8b372..c417f20 100644 --- a/scaffolder.php +++ b/scaffolder.php @@ -12,4 +12,5 @@ 'use-phpunit' => false, 'use-psalm' => false, 'use-phpstan' => true, + 'use-infection' => false, ]; diff --git a/src/Change/PrepareDocsInternals.php b/src/Change/PrepareDocsInternals.php index 99bb445..4526974 100644 --- a/src/Change/PrepareDocsInternals.php +++ b/src/Change/PrepareDocsInternals.php @@ -12,7 +12,6 @@ use Vjik\Scaffolder\Change; use Vjik\Scaffolder\Cli; use Vjik\Scaffolder\Context; -use Vjik\Scaffolder\Fact\Title; use function sprintf; 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/changes.php b/src/changes.php index 5f8546a..875fba5 100644 --- a/src/changes.php +++ b/src/changes.php @@ -8,6 +8,7 @@ use Phptg\Scaffolder\Change\PrepareReadme; use Phptg\Scaffolder\Change\PrepareRectorConfiguration; use Phptg\Scaffolder\Fact\UseComposerDependencyAnalyser; +use Phptg\Scaffolder\Fact\UseInfection; use Phptg\Scaffolder\Fact\UsePhpCsFixer; use Phptg\Scaffolder\Fact\UsePhpStan; use Phptg\Scaffolder\Fact\UsePhpUnit; @@ -75,6 +76,11 @@ $new['scripts']['dependency-analyser'] ??= 'composer-dependency-analyser'; } + // Infection + if ($context->getFact(UseInfection::class)) { + $new['scripts']['infection'] ??= 'infection --threads=max'; + } + return $new; }, ), @@ -123,5 +129,12 @@ ], UseComposerDependencyAnalyser::class, ), + new ChangeIf( + [ + new CopyFileIfNotExists($files . '/tools/infection/composer.json', 'tools/infection/composer.json'), + new CopyFileIfNotExists($files . '/infection.json.dist', 'infection.json.dist'), + ], + UseInfection::class, + ), new CopyFileIfNotExists($files . '/.gitattributes', '.gitattributes'), ]; diff --git a/src/facts.php b/src/facts.php index 49027ab..fae46e5 100644 --- a/src/facts.php +++ b/src/facts.php @@ -6,6 +6,7 @@ return [ Fact\UseComposerDependencyAnalyser::class, + Fact\UseInfection::class, Fact\UsePhpCsFixer::class, Fact\UsePhpStan::class, Fact\UsePhpUnit::class, From 437db3cefaf71d67ada92a00cd20e251a6301b29 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 15:09:21 +0300 Subject: [PATCH 21/30] Add GitHub Actions workflow for PHP testing and coverage --- composer.lock | 8 ++-- files/.github/workflows/build.yml | 48 +++++++++++++++++++++++ src/Change/PrepareGitHubWorkflowBuild.php | 45 +++++++++++++++++++++ src/changes.php | 6 ++- 4 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 files/.github/workflows/build.yml create mode 100644 src/Change/PrepareGitHubWorkflowBuild.php diff --git a/composer.lock b/composer.lock index 48cdc4d..54cf978 100644 --- a/composer.lock +++ b/composer.lock @@ -881,12 +881,12 @@ "source": { "type": "git", "url": "https://github.com/vjik/scaffolder.git", - "reference": "899f01b6db77ebaa069076c92de155a7b3383352" + "reference": "3b7157a2ab700242b88f7fae08db0ef45a558f39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vjik/scaffolder/zipball/899f01b6db77ebaa069076c92de155a7b3383352", - "reference": "899f01b6db77ebaa069076c92de155a7b3383352", + "url": "https://api.github.com/repos/vjik/scaffolder/zipball/3b7157a2ab700242b88f7fae08db0ef45a558f39", + "reference": "3b7157a2ab700242b88f7fae08db0ef45a558f39", "shasum": "" }, "require": { @@ -921,7 +921,7 @@ "issues": "https://github.com/vjik/scaffolder/issues", "source": "https://github.com/vjik/scaffolder/tree/master" }, - "time": "2026-01-04T10:08:33+00:00" + "time": "2026-01-04T12:07:35+00:00" }, { "name": "yiisoft/strings", 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/src/Change/PrepareGitHubWorkflowBuild.php b/src/Change/PrepareGitHubWorkflowBuild.php new file mode 100644 index 0000000..8c1aa90 --- /dev/null +++ b/src/Change/PrepareGitHubWorkflowBuild.php @@ -0,0 +1,45 @@ +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), + ), + ); + $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/changes.php b/src/changes.php index 875fba5..a1fc783 100644 --- a/src/changes.php +++ b/src/changes.php @@ -4,6 +4,7 @@ use Phptg\Scaffolder\Change\PrepareChangelog; use Phptg\Scaffolder\Change\PrepareDocsInternals; +use Phptg\Scaffolder\Change\PrepareGitHubWorkflowBuild; use Phptg\Scaffolder\Change\PrepareGitignore; use Phptg\Scaffolder\Change\PrepareReadme; use Phptg\Scaffolder\Change\PrepareRectorConfiguration; @@ -101,7 +102,10 @@ new PrepareChangelog(), new CopyFile($files . '/tools/.gitignore', 'tools/.gitignore'), new ChangeIf( - new CopyFileIfNotExists($files . '/phpunit.xml.dist', 'phpunit.xml.dist'), + [ + new CopyFileIfNotExists($files . '/phpunit.xml.dist', 'phpunit.xml.dist'), + new PrepareGitHubWorkflowBuild(), + ], UsePhpUnit::class, ), new ChangeIf( From baf18c728e3c6a59ca49ca67a02f6817c8a82ad8 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 15:12:29 +0300 Subject: [PATCH 22/30] Add GitHub Actions workflow for Composer dependency analysis --- .../composer-dependency-analyser.yml | 38 +++++++++++++++ src/Change/PrepareGitHubWorkflowBuild.php | 2 + ...tHubWorkflowComposerDependencyAnalyser.php | 47 +++++++++++++++++++ src/changes.php | 2 + 4 files changed, 89 insertions(+) create mode 100644 files/.github/workflows/composer-dependency-analyser.yml create mode 100644 src/Change/PrepareGitHubWorkflowComposerDependencyAnalyser.php diff --git a/files/.github/workflows/composer-dependency-analyser.yml b/files/.github/workflows/composer-dependency-analyser.yml new file mode 100644 index 0000000..90fc024 --- /dev/null +++ b/files/.github/workflows/composer-dependency-analyser.yml @@ -0,0 +1,38 @@ +on: + pull_request: + push: + branches: [ 'master' ] + +name: Composer Dependency Analyser + +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/src/Change/PrepareGitHubWorkflowBuild.php b/src/Change/PrepareGitHubWorkflowBuild.php index 8c1aa90..720422e 100644 --- a/src/Change/PrepareGitHubWorkflowBuild.php +++ b/src/Change/PrepareGitHubWorkflowBuild.php @@ -31,6 +31,8 @@ public function decide(Context $context): callable|array|null $context->getFact(MinorPhpVersionRange::class), ), ); + + /** @var string $new */ $new = preg_replace('/^(\s*php:\n)(?:\s*-\s*".*"\n)+/m', "$1$phpMatrix\n", $new, 1); if ($original === $new) { diff --git a/src/Change/PrepareGitHubWorkflowComposerDependencyAnalyser.php b/src/Change/PrepareGitHubWorkflowComposerDependencyAnalyser.php new file mode 100644 index 0000000..11f50ef --- /dev/null +++ b/src/Change/PrepareGitHubWorkflowComposerDependencyAnalyser.php @@ -0,0 +1,47 @@ +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), + ), + ); + + /** @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/changes.php b/src/changes.php index a1fc783..c0236ee 100644 --- a/src/changes.php +++ b/src/changes.php @@ -5,6 +5,7 @@ use Phptg\Scaffolder\Change\PrepareChangelog; use Phptg\Scaffolder\Change\PrepareDocsInternals; use Phptg\Scaffolder\Change\PrepareGitHubWorkflowBuild; +use Phptg\Scaffolder\Change\PrepareGitHubWorkflowComposerDependencyAnalyser; use Phptg\Scaffolder\Change\PrepareGitignore; use Phptg\Scaffolder\Change\PrepareReadme; use Phptg\Scaffolder\Change\PrepareRectorConfiguration; @@ -130,6 +131,7 @@ [ 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, ), From 0df89c0cb01bbb1f08d0dcec008a8a06af02215c Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 15:14:05 +0300 Subject: [PATCH 23/30] Add Composer Dependency Analyser workflow and refactor variable names for clarity --- .../composer-dependency-analyser.yml | 35 +++++++++++++++++++ composer.json | 2 +- src/Change/PrepareChangelog.php | 6 ++-- src/Change/PrepareDocsInternals.php | 6 ++-- src/Change/PrepareGitignore.php | 6 ++-- src/Change/PrepareReadme.php | 6 ++-- src/Change/PrepareRectorConfiguration.php | 6 ++-- 7 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/composer-dependency-analyser.yml diff --git a/.github/workflows/composer-dependency-analyser.yml b/.github/workflows/composer-dependency-analyser.yml new file mode 100644 index 0000000..2d9beab --- /dev/null +++ b/.github/workflows/composer-dependency-analyser.yml @@ -0,0 +1,35 @@ +on: + pull_request: + push: + branches: [ 'master' ] + +name: Composer Dependency Analyser + +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/composer.json b/composer.json index 908a317..efaf029 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "source": "https://github.com/phptg/scaffolder" }, "require": { - "php": "^8.4", + "php": "8.4.*", "ext-filter": "*", "symfony/console": "^8.0.3", "vjik/scaffolder": "dev-master" diff --git a/src/Change/PrepareChangelog.php b/src/Change/PrepareChangelog.php index 752beb8..5a642b9 100644 --- a/src/Change/PrepareChangelog.php +++ b/src/Change/PrepareChangelog.php @@ -17,10 +17,10 @@ public function decide(Context $context): callable|array|null { - $old = $context->tryReadFile(self::FILE); - $new = $old ?? $this->createNew($context); + $original = $context->tryReadFile(self::FILE); + $new = $original ?? $this->createNew($context); - if ($old === $new) { + if ($original === $new) { return null; } diff --git a/src/Change/PrepareDocsInternals.php b/src/Change/PrepareDocsInternals.php index 4526974..a8eb838 100644 --- a/src/Change/PrepareDocsInternals.php +++ b/src/Change/PrepareDocsInternals.php @@ -21,10 +21,10 @@ public function decide(Context $context): callable|array|null { - $old = $context->tryReadFile(self::FILE); - $new = $old ?? $this->createNew($context); + $original = $context->tryReadFile(self::FILE); + $new = $original ?? $this->createNew($context); - if ($old === $new) { + if ($original === $new) { return null; } diff --git a/src/Change/PrepareGitignore.php b/src/Change/PrepareGitignore.php index 4dd72e6..a5af9c3 100644 --- a/src/Change/PrepareGitignore.php +++ b/src/Change/PrepareGitignore.php @@ -18,10 +18,10 @@ public function decide(Context $context): callable|array|null { - $old = $context->tryReadFile(self::FILE); - $new = $old ?? $this->createNew($context); + $original = $context->tryReadFile(self::FILE); + $new = $original ?? $this->createNew($context); - if ($old === $new) { + if ($original === $new) { return null; } diff --git a/src/Change/PrepareReadme.php b/src/Change/PrepareReadme.php index 89df535..2d2b6a7 100644 --- a/src/Change/PrepareReadme.php +++ b/src/Change/PrepareReadme.php @@ -22,10 +22,10 @@ public function decide(Context $context): callable|array|null { - $old = $context->tryReadFile(self::FILE); - $new = $old ?? $this->createNew($context); + $original = $context->tryReadFile(self::FILE); + $new = $original ?? $this->createNew($context); - if ($old === $new) { + if ($original === $new) { return null; } diff --git a/src/Change/PrepareRectorConfiguration.php b/src/Change/PrepareRectorConfiguration.php index ea3cbf6..0735a27 100644 --- a/src/Change/PrepareRectorConfiguration.php +++ b/src/Change/PrepareRectorConfiguration.php @@ -19,10 +19,10 @@ public function decide(Context $context): callable|array|null { - $old = $context->tryReadFile(self::FILE); + $original = $context->tryReadFile(self::FILE); /** @var string $new */ - $new = $old ?? file_get_contents(dirname(self::FILE, 2) . '/files/' . self::FILE); + $new = $original ?? file_get_contents(dirname(self::FILE, 2) . '/files/' . self::FILE); $phpSet = $this->getPhpSet($context); if ($phpSet === null) { @@ -36,7 +36,7 @@ public function decide(Context $context): callable|array|null $new, ); - if ($old === $new) { + if ($original === $new) { return null; } From a04f4ecdc9212a539e6774ac81e9800db0d5b88a Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 15:17:46 +0300 Subject: [PATCH 24/30] Add GitHub Actions workflow for mutation testing and integrate PHP version handling --- files/.github/workflows/mutation.yml | 38 ++++++++++++++++ src/Change/PrepareGitHubWorkflowMutation.php | 46 ++++++++++++++++++++ src/changes.php | 2 + 3 files changed, 86 insertions(+) create mode 100644 files/.github/workflows/mutation.yml create mode 100644 src/Change/PrepareGitHubWorkflowMutation.php 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/src/Change/PrepareGitHubWorkflowMutation.php b/src/Change/PrepareGitHubWorkflowMutation.php new file mode 100644 index 0000000..81d412b --- /dev/null +++ b/src/Change/PrepareGitHubWorkflowMutation.php @@ -0,0 +1,46 @@ +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) { + return null; + } + + $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/changes.php b/src/changes.php index c0236ee..dcb123a 100644 --- a/src/changes.php +++ b/src/changes.php @@ -6,6 +6,7 @@ use Phptg\Scaffolder\Change\PrepareDocsInternals; use Phptg\Scaffolder\Change\PrepareGitHubWorkflowBuild; use Phptg\Scaffolder\Change\PrepareGitHubWorkflowComposerDependencyAnalyser; +use Phptg\Scaffolder\Change\PrepareGitHubWorkflowMutation; use Phptg\Scaffolder\Change\PrepareGitignore; use Phptg\Scaffolder\Change\PrepareReadme; use Phptg\Scaffolder\Change\PrepareRectorConfiguration; @@ -139,6 +140,7 @@ [ new CopyFileIfNotExists($files . '/tools/infection/composer.json', 'tools/infection/composer.json'), new CopyFileIfNotExists($files . '/infection.json.dist', 'infection.json.dist'), + new PrepareGitHubWorkflowMutation(), ], UseInfection::class, ), From a35b9ebe4e75ab326fb8030c8af9080ab028aa1d Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 15:20:16 +0300 Subject: [PATCH 25/30] Add GitHub Actions workflow for Psalm static analysis --- files/.github/workflows/psalm.yml | 40 +++++++++++++++++++ src/Change/PrepareGitHubWorkflowPsalm.php | 47 +++++++++++++++++++++++ src/changes.php | 2 + 3 files changed, 89 insertions(+) create mode 100644 files/.github/workflows/psalm.yml create mode 100644 src/Change/PrepareGitHubWorkflowPsalm.php 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/src/Change/PrepareGitHubWorkflowPsalm.php b/src/Change/PrepareGitHubWorkflowPsalm.php new file mode 100644 index 0000000..0b2ab70 --- /dev/null +++ b/src/Change/PrepareGitHubWorkflowPsalm.php @@ -0,0 +1,47 @@ +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), + ), + ); + + /** @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/changes.php b/src/changes.php index dcb123a..e3892fe 100644 --- a/src/changes.php +++ b/src/changes.php @@ -7,6 +7,7 @@ use Phptg\Scaffolder\Change\PrepareGitHubWorkflowBuild; use Phptg\Scaffolder\Change\PrepareGitHubWorkflowComposerDependencyAnalyser; use Phptg\Scaffolder\Change\PrepareGitHubWorkflowMutation; +use Phptg\Scaffolder\Change\PrepareGitHubWorkflowPsalm; use Phptg\Scaffolder\Change\PrepareGitignore; use Phptg\Scaffolder\Change\PrepareReadme; use Phptg\Scaffolder\Change\PrepareRectorConfiguration; @@ -114,6 +115,7 @@ [ new CopyFileIfNotExists($files . '/tools/psalm/composer.json', 'tools/psalm/composer.json'), new CopyFileIfNotExists($files . '/psalm.xml', 'psalm.xml'), + new PrepareGitHubWorkflowPsalm(), ], UsePsalm::class, ), From 7f0f119a06979488cfe80d71c040d56ab1bc0cfa Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 15:23:27 +0300 Subject: [PATCH 26/30] Add GitHub Actions workflow for PHPStan static analysis with PHP version matrix --- .github/workflows/phpstan.yml | 37 ++++++++++++++++ files/.github/workflows/phpstan.yml | 40 ++++++++++++++++++ src/Change/PrepareGitHubWorkflowPhpstan.php | 47 +++++++++++++++++++++ src/changes.php | 6 ++- 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/phpstan.yml create mode 100644 files/.github/workflows/phpstan.yml create mode 100644 src/Change/PrepareGitHubWorkflowPhpstan.php diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..c436ca7 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,37 @@ +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.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/files/.github/workflows/phpstan.yml b/files/.github/workflows/phpstan.yml new file mode 100644 index 0000000..507ab39 --- /dev/null +++ b/files/.github/workflows/phpstan.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/phpstan analyse --configuration=phpstan.neon diff --git a/src/Change/PrepareGitHubWorkflowPhpstan.php b/src/Change/PrepareGitHubWorkflowPhpstan.php new file mode 100644 index 0000000..1668ce7 --- /dev/null +++ b/src/Change/PrepareGitHubWorkflowPhpstan.php @@ -0,0 +1,47 @@ +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), + ), + ); + + /** @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/changes.php b/src/changes.php index e3892fe..943e64a 100644 --- a/src/changes.php +++ b/src/changes.php @@ -7,6 +7,7 @@ use Phptg\Scaffolder\Change\PrepareGitHubWorkflowBuild; 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; @@ -120,7 +121,10 @@ UsePsalm::class, ), new ChangeIf( - new CopyFileIfNotExists($files . '/phpstan.neon', 'phpstan.neon'), + [ + new CopyFileIfNotExists($files . '/phpstan.neon', 'phpstan.neon'), + new PrepareGitHubWorkflowPhpstan(), + ], UsePhpStan::class, ), new ChangeIf( From a10292f40e799b5484e42b17552f609d1a3c7330 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 15:33:59 +0300 Subject: [PATCH 27/30] Add GitHub Actions workflow for PHP CS Fixer and Rector --- .github/workflows/code-style.yml | 38 ++++++++++++++ files/.github/workflows/code-style.yml | 38 ++++++++++++++ src/Change/PrepareDocsInternals.php | 19 +++---- src/Change/PrepareGitHubWorkflowCodeStyle.php | 49 +++++++++++++++++++ src/Fact/UsePhpCsFixer.php | 34 ------------- src/changes.php | 20 +++----- src/facts.php | 1 - 7 files changed, 140 insertions(+), 59 deletions(-) create mode 100644 .github/workflows/code-style.yml create mode 100644 files/.github/workflows/code-style.yml create mode 100644 src/Change/PrepareGitHubWorkflowCodeStyle.php delete mode 100644 src/Fact/UsePhpCsFixer.php diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml new file mode 100644 index 0000000..5c46f98 --- /dev/null +++ b/.github/workflows/code-style.yml @@ -0,0 +1,38 @@ +name: cs fixer + +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/files/.github/workflows/code-style.yml b/files/.github/workflows/code-style.yml new file mode 100644 index 0000000..56e1e13 --- /dev/null +++ b/files/.github/workflows/code-style.yml @@ -0,0 +1,38 @@ +name: cs fixer + +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/src/Change/PrepareDocsInternals.php b/src/Change/PrepareDocsInternals.php index a8eb838..91edafc 100644 --- a/src/Change/PrepareDocsInternals.php +++ b/src/Change/PrepareDocsInternals.php @@ -5,7 +5,6 @@ namespace Phptg\Scaffolder\Change; use Phptg\Scaffolder\Fact\UseComposerDependencyAnalyser; -use Phptg\Scaffolder\Fact\UsePhpCsFixer; use Phptg\Scaffolder\Fact\UsePhpStan; use Phptg\Scaffolder\Fact\UsePhpUnit; use Phptg\Scaffolder\Fact\UsePsalm; @@ -74,18 +73,16 @@ private function createNew(Context $context): string BLOCK; } - if ($context->getFact(UsePhpCsFixer::class)) { - $blocks[] = <<getFact(UseComposerDependencyAnalyser::class)) { $blocks[] = <<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) { + return null; + } + + /** @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/Fact/UsePhpCsFixer.php b/src/Fact/UsePhpCsFixer.php deleted file mode 100644 index d025960..0000000 --- a/src/Fact/UsePhpCsFixer.php +++ /dev/null @@ -1,34 +0,0 @@ - - */ -final class UsePhpCsFixer extends Fact -{ - public const string VALUE_OPTION = 'use-phpcsfixer'; - - 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 943e64a..1ec3e2c 100644 --- a/src/changes.php +++ b/src/changes.php @@ -5,6 +5,7 @@ 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; @@ -14,7 +15,6 @@ use Phptg\Scaffolder\Change\PrepareRectorConfiguration; use Phptg\Scaffolder\Fact\UseComposerDependencyAnalyser; use Phptg\Scaffolder\Fact\UseInfection; -use Phptg\Scaffolder\Fact\UsePhpCsFixer; use Phptg\Scaffolder\Fact\UsePhpStan; use Phptg\Scaffolder\Fact\UsePhpUnit; use Phptg\Scaffolder\Fact\UsePsalm; @@ -55,6 +55,9 @@ $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'; @@ -71,11 +74,6 @@ $new['require-dev']['phpunit/phpunit'] ??= '^11.5.46'; } - // PHP CS Fixer - if ($context->getFact(UsePhpCsFixer::class)) { - $new['scripts']['cs-fix'] ??= 'php-cs-fixer fix'; - } - // Composer Dependency Analyser if ($context->getFact(UseComposerDependencyAnalyser::class)) { $new['scripts']['dependency-analyser'] ??= 'composer-dependency-analyser'; @@ -127,13 +125,9 @@ ], UsePhpStan::class, ), - new ChangeIf( - [ - 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'), - ], - UsePhpCsFixer::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'), diff --git a/src/facts.php b/src/facts.php index fae46e5..9842c05 100644 --- a/src/facts.php +++ b/src/facts.php @@ -7,7 +7,6 @@ return [ Fact\UseComposerDependencyAnalyser::class, Fact\UseInfection::class, - Fact\UsePhpCsFixer::class, Fact\UsePhpStan::class, Fact\UsePhpUnit::class, Fact\UsePsalm::class, From 24f3294ec3c2bb87afcd300d041e4a204bd108e2 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 15:40:57 +0300 Subject: [PATCH 28/30] Update Dockerfile to use PHP 8.4 CLI base image --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b13a546259890556499174a9d2f5d410501da87d Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 18:42:31 +0300 Subject: [PATCH 29/30] Refactor PHP version handling in GitHub workflows to ensure compatibility checks --- Makefile | 13 +++++++++++++ composer.json | 2 +- composer.lock | 12 ++++++------ src/Change/PrepareGitHubWorkflowBuild.php | 7 ++++--- src/Change/PrepareGitHubWorkflowCodeStyle.php | 18 ++++++++---------- ...itHubWorkflowComposerDependencyAnalyser.php | 7 ++++--- src/Change/PrepareGitHubWorkflowMutation.php | 11 ++++------- src/Change/PrepareGitHubWorkflowPhpstan.php | 7 ++++--- src/Change/PrepareGitHubWorkflowPsalm.php | 7 ++++--- 9 files changed, 48 insertions(+), 36 deletions(-) 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/composer.json b/composer.json index efaf029..2a0bfa1 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "source": "https://github.com/phptg/scaffolder" }, "require": { - "php": "8.4.*", + "php": "~8.4.0", "ext-filter": "*", "symfony/console": "^8.0.3", "vjik/scaffolder": "dev-master" diff --git a/composer.lock b/composer.lock index 54cf978..1de14bf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bb5a146140ee14dda2d09b93517e34bb", + "content-hash": "4e9945344ab966120fc8a21a7cdc7738", "packages": [ { "name": "composer/semver", @@ -881,12 +881,12 @@ "source": { "type": "git", "url": "https://github.com/vjik/scaffolder.git", - "reference": "3b7157a2ab700242b88f7fae08db0ef45a558f39" + "reference": "e46a28d0b531decd8162c83d015827f3f06bff54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vjik/scaffolder/zipball/3b7157a2ab700242b88f7fae08db0ef45a558f39", - "reference": "3b7157a2ab700242b88f7fae08db0ef45a558f39", + "url": "https://api.github.com/repos/vjik/scaffolder/zipball/e46a28d0b531decd8162c83d015827f3f06bff54", + "reference": "e46a28d0b531decd8162c83d015827f3f06bff54", "shasum": "" }, "require": { @@ -921,7 +921,7 @@ "issues": "https://github.com/vjik/scaffolder/issues", "source": "https://github.com/vjik/scaffolder/tree/master" }, - "time": "2026-01-04T12:07:35+00:00" + "time": "2026-01-04T15:31:22+00:00" }, { "name": "yiisoft/strings", @@ -1174,7 +1174,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^8.4", + "php": "~8.4.0", "ext-filter": "*" }, "platform-dev": {}, diff --git a/src/Change/PrepareGitHubWorkflowBuild.php b/src/Change/PrepareGitHubWorkflowBuild.php index 720422e..41e948a 100644 --- a/src/Change/PrepareGitHubWorkflowBuild.php +++ b/src/Change/PrepareGitHubWorkflowBuild.php @@ -31,9 +31,10 @@ public function decide(Context $context): callable|array|null $context->getFact(MinorPhpVersionRange::class), ), ); - - /** @var string $new */ - $new = preg_replace('/^(\s*php:\n)(?:\s*-\s*".*"\n)+/m', "$1$phpMatrix\n", $new, 1); + 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; diff --git a/src/Change/PrepareGitHubWorkflowCodeStyle.php b/src/Change/PrepareGitHubWorkflowCodeStyle.php index 92b4d87..f881235 100644 --- a/src/Change/PrepareGitHubWorkflowCodeStyle.php +++ b/src/Change/PrepareGitHubWorkflowCodeStyle.php @@ -25,18 +25,16 @@ public function decide(Context $context): callable|array|null $new = $original ?? file_get_contents(dirname(__DIR__, 2) . '/files/' . self::FILE); $phpVersion = $context->getFact(LowestMinorPhpVersion::class); - if ($phpVersion === MinorPhpVersion::UNKNOWN) { - return null; + if ($phpVersion !== MinorPhpVersion::UNKNOWN) { + /** @var string $new */ + $new = preg_replace( + '/^(\s*php-version:\s*)(["\']?)[\d\.]+\2/m', + '${1}' . $phpVersion->value, + $new, + 1, + ); } - /** @var string $new */ - $new = preg_replace( - '/^(\s*php-version:\s*)(["\']?)[\d\.]+\2/m', - '${1}' . $phpVersion->value, - $new, - 1, - ); - if ($original === $new) { return null; } diff --git a/src/Change/PrepareGitHubWorkflowComposerDependencyAnalyser.php b/src/Change/PrepareGitHubWorkflowComposerDependencyAnalyser.php index 11f50ef..334f4aa 100644 --- a/src/Change/PrepareGitHubWorkflowComposerDependencyAnalyser.php +++ b/src/Change/PrepareGitHubWorkflowComposerDependencyAnalyser.php @@ -31,9 +31,10 @@ public function decide(Context $context): callable|array|null $context->getFact(MinorPhpVersionRange::class), ), ); - - /** @var string $new */ - $new = preg_replace('/^(\s*php:\n)(?:\s*-\s*".*"\n)+/m', "$1$phpMatrix\n", $new, 1); + 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; diff --git a/src/Change/PrepareGitHubWorkflowMutation.php b/src/Change/PrepareGitHubWorkflowMutation.php index 81d412b..c832c45 100644 --- a/src/Change/PrepareGitHubWorkflowMutation.php +++ b/src/Change/PrepareGitHubWorkflowMutation.php @@ -25,15 +25,12 @@ public function decide(Context $context): callable|array|null $new = $original ?? file_get_contents(dirname(__DIR__, 2) . '/files/' . self::FILE); $phpVersion = $context->getFact(HighestMinorPhpVersion::class); - if ($phpVersion === MinorPhpVersion::UNKNOWN) { - return null; + 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); } - $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; } diff --git a/src/Change/PrepareGitHubWorkflowPhpstan.php b/src/Change/PrepareGitHubWorkflowPhpstan.php index 1668ce7..265a574 100644 --- a/src/Change/PrepareGitHubWorkflowPhpstan.php +++ b/src/Change/PrepareGitHubWorkflowPhpstan.php @@ -31,9 +31,10 @@ public function decide(Context $context): callable|array|null $context->getFact(MinorPhpVersionRange::class), ), ); - - /** @var string $new */ - $new = preg_replace('/^(\s*php:\n)(?:\s*-\s*".*"\n)+/m', "$1$phpMatrix\n", $new, 1); + 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; diff --git a/src/Change/PrepareGitHubWorkflowPsalm.php b/src/Change/PrepareGitHubWorkflowPsalm.php index 0b2ab70..4555b71 100644 --- a/src/Change/PrepareGitHubWorkflowPsalm.php +++ b/src/Change/PrepareGitHubWorkflowPsalm.php @@ -31,9 +31,10 @@ public function decide(Context $context): callable|array|null $context->getFact(MinorPhpVersionRange::class), ), ); - - /** @var string $new */ - $new = preg_replace('/^(\s*php:\n)(?:\s*-\s*".*"\n)+/m', "$1$phpMatrix\n", $new, 1); + 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; From 4d3ee5e8963179f4ea294a60f669cd3a32edfa61 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 4 Jan 2026 18:57:30 +0300 Subject: [PATCH 30/30] Add docker build & push. Rename workflows for clarity and consistency --- .github/workflows/code-style.yml | 2 +- .../composer-dependency-analyser.yml | 4 +- .github/workflows/docker.yml | 51 +++++++++++++++++++ .github/workflows/phpstan.yml | 6 +-- files/.github/workflows/code-style.yml | 2 +- .../composer-dependency-analyser.yml | 4 +- files/.github/workflows/phpstan.yml | 6 +-- 7 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml index 5c46f98..3a45b5d 100644 --- a/.github/workflows/code-style.yml +++ b/.github/workflows/code-style.yml @@ -1,4 +1,4 @@ -name: cs fixer +name: Code Style on: [ pull_request_target ] diff --git a/.github/workflows/composer-dependency-analyser.yml b/.github/workflows/composer-dependency-analyser.yml index 2d9beab..af644cb 100644 --- a/.github/workflows/composer-dependency-analyser.yml +++ b/.github/workflows/composer-dependency-analyser.yml @@ -1,10 +1,10 @@ +name: Composer Dependency Analyser + on: pull_request: push: branches: [ 'master' ] -name: Composer Dependency Analyser - jobs: composer-require-checker: name: PHP ${{ matrix.php }}-${{ matrix.os }} 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 index c436ca7..4f29fb0 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -1,13 +1,13 @@ +name: PHPStan + on: pull_request: push: branches: - master -name: static analysis - jobs: - psalm: + phpstan: name: PHP ${{ matrix.php }}-${{ matrix.os }} runs-on: ${{ matrix.os }} diff --git a/files/.github/workflows/code-style.yml b/files/.github/workflows/code-style.yml index 56e1e13..81fda80 100644 --- a/files/.github/workflows/code-style.yml +++ b/files/.github/workflows/code-style.yml @@ -1,4 +1,4 @@ -name: cs fixer +name: Code Style on: [ pull_request_target ] diff --git a/files/.github/workflows/composer-dependency-analyser.yml b/files/.github/workflows/composer-dependency-analyser.yml index 90fc024..e11b7c1 100644 --- a/files/.github/workflows/composer-dependency-analyser.yml +++ b/files/.github/workflows/composer-dependency-analyser.yml @@ -1,10 +1,10 @@ +name: Composer Dependency Analyser + on: pull_request: push: branches: [ 'master' ] -name: Composer Dependency Analyser - jobs: composer-require-checker: name: PHP ${{ matrix.php }}-${{ matrix.os }} diff --git a/files/.github/workflows/phpstan.yml b/files/.github/workflows/phpstan.yml index 507ab39..cca02e0 100644 --- a/files/.github/workflows/phpstan.yml +++ b/files/.github/workflows/phpstan.yml @@ -1,13 +1,13 @@ +name: PHPStan + on: pull_request: push: branches: - master -name: static analysis - jobs: - psalm: + phpstan: name: PHP ${{ matrix.php }}-${{ matrix.os }} runs-on: ${{ matrix.os }}