diff --git a/.changelog/phar-build-parity.md b/.changelog/phar-build-parity.md new file mode 100644 index 0000000..9f632e1 --- /dev/null +++ b/.changelog/phar-build-parity.md @@ -0,0 +1,7 @@ +--- +category: fixed +type: patch +--- + +Compare the committed PHAR's complete stub and contents with the fresh reviewed +build before execution, rejecting forged self-reported distribution manifests. diff --git a/.changelog/phar-cache-lifetime.md b/.changelog/phar-cache-lifetime.md new file mode 100644 index 0000000..de26a02 --- /dev/null +++ b/.changelog/phar-cache-lifetime.md @@ -0,0 +1,7 @@ +--- +category: fixed +type: patch +--- + +Keep compiled formatter caches inside the extracted runtime so repeated checks +and fixes clean them up, preserving source-install cache reuse and explicit overrides. diff --git a/.changelog/phar-consumer-rules.md b/.changelog/phar-consumer-rules.md new file mode 100644 index 0000000..ddb83ed --- /dev/null +++ b/.changelog/phar-consumer-rules.md @@ -0,0 +1,7 @@ +--- +category: fixed +type: patch +--- + +Document explicit consumer ECS autoloading and qualify project rules and +development rule packages against the compiled executable with real Composer mappings. diff --git a/.changelog/phar-distribution-investigation.md b/.changelog/phar-distribution-investigation.md new file mode 100644 index 0000000..ff16c6c --- /dev/null +++ b/.changelog/phar-distribution-investigation.md @@ -0,0 +1,8 @@ +--- +category: added +type: minor +--- + +Build an executable PHAR with PHP tooling and a direct Composer binary target, +move bundled libraries to development dependencies, preserve all copyable +resources, and ship the ready executable in standard Composer distributions. diff --git a/.changelog/phar-locked-composer.md b/.changelog/phar-locked-composer.md new file mode 100644 index 0000000..bb07be0 --- /dev/null +++ b/.changelog/phar-locked-composer.md @@ -0,0 +1,7 @@ +--- +category: fixed +type: patch +--- + +Build with the locked Composer executable so global Composer updates cannot +change the compiled payload's generated lock metadata. diff --git a/.gitattributes b/.gitattributes index 8f3cc00..5aa59e9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,7 @@ * text=auto *.php text eol=lf /bin/code-standards text eol=lf +/builds/code-standards binary # Local state and synchronized copies never belong to package distributions. /.git export-ignore @@ -12,6 +13,7 @@ # Contributor-only configuration and test infrastructure. /tests/ export-ignore +/scripts/phar/ export-ignore /.github/workflows/ export-ignore /AGENTS.md export-ignore /skills.json export-ignore @@ -21,4 +23,7 @@ /.gitattributes export-ignore /.changelog/ export-ignore -# Keep src/, bin/, config/, resources/, composer.lock, README and its Dash image. +# Ready distributions retain the PHAR, manifests, complete resources and documentation. +/src/ export-ignore +/config/ export-ignore +/bin/ export-ignore diff --git a/.github/workflows/code-standards-analysis.yml b/.github/workflows/code-standards-analysis.yml index 1f3bb32..26e9fc1 100644 --- a/.github/workflows/code-standards-analysis.yml +++ b/.github/workflows/code-standards-analysis.yml @@ -103,7 +103,7 @@ jobs: env: COMPOSER_HOME: ${{ runner.temp }}/standards-analysis-composer-home COMPOSER_CACHE_DIR: ${{ runner.temp }}/standards-analysis-composer-cache - run: composer install --no-dev --no-plugins --no-scripts --no-interaction --prefer-dist --no-progress + run: composer install --no-plugins --no-scripts --no-interaction --prefer-dist --no-progress - name: Check out exact consumer head if: inputs.magic-numbers || inputs.rector uses: actions/checkout@v7 diff --git a/.github/workflows/code-standards-check.yml b/.github/workflows/code-standards-check.yml index 5f09ca2..0d18183 100644 --- a/.github/workflows/code-standards-check.yml +++ b/.github/workflows/code-standards-check.yml @@ -79,7 +79,7 @@ jobs: env: COMPOSER_HOME: ${{ runner.temp }}/standards-composer-home COMPOSER_CACHE_DIR: ${{ runner.temp }}/standards-composer-cache - run: composer install --no-dev --no-plugins --no-scripts --no-interaction --prefer-dist --no-progress + run: composer install --no-plugins --no-scripts --no-interaction --prefer-dist --no-progress - name: Check out exact consumer head uses: actions/checkout@v7 with: diff --git a/.github/workflows/repository-ci.yml b/.github/workflows/repository-ci.yml index 53208c4..5ec60a6 100644 --- a/.github/workflows/repository-ci.yml +++ b/.github/workflows/repository-ci.yml @@ -36,6 +36,25 @@ jobs: run: | php "$STANDARDS_ROOT/bin/code-standards" normalize-composer "--directory=$STANDARDS_DIRECTORY" - run: composer check + - name: Qualify ready PHAR distribution and native runtime + env: + COMPOSER_HOME: ${{ runner.temp }}/standards-phar-composer-home + COMPOSER_CACHE_DIR: ${{ runner.temp }}/standards-composer-cache + run: | + php -d phar.readonly=0 scripts/phar/build.php "$RUNNER_TEMP/code-standards" + php scripts/phar/verify.php "$RUNNER_TEMP/code-standards" + - name: Retain the reviewed PHP 8.3 build + if: matrix.php == '8.3' + uses: actions/upload-artifact@v7 + with: + name: code-standards-${{ github.sha }} + path: ${{ runner.temp }}/code-standards + compression-level: 0 + if-no-files-found: error + - name: Qualify the committed Composer distribution + run: | + php scripts/phar/compare.php "$RUNNER_TEMP/code-standards" builds/code-standards + php scripts/phar/verify.php builds/code-standards - name: Provision isolated Twig evaluation dependencies run: composer install --working-dir=tests/Evaluation/Twig --no-dev --no-plugins --no-scripts --no-interaction --prefer-dist --no-progress env: diff --git a/.gitignore b/.gitignore index e3f4124..f469668 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /vendor/ +/builds/*.phar /.build/ /.phpunit.cache/ diff --git a/README.md b/README.md index 920b195..fca05b0 100644 --- a/README.md +++ b/README.md @@ -30,19 +30,32 @@ composer require --dev fast-forward/code-standards vendor/bin/code-standards check --all ``` -CPX can invoke the installed package directly with -`cpx fast-forward/code-standards list`. The CLI resolves Composer dependencies -from its installation, including when run outside the package checkout. - -The GitHub `v0` workflow channel and the Composer package are separate -distribution mechanisms. +The compiled executable is installed directly through Composer's binary proxy. +Optional scripts keep checks convenient without building the tool in consumers: + +```json +{ + "scripts": { + "standards:check": "code-standards check --all", + "standards:fix": "code-standards fix --all" + } +} +``` -Requires PHP 8.3 or later in the 8.x series, Git and Composer runtime API 2.2+ -for installed binary proxies. Composer resolves the tool with the project's -dependency constraints; syntax is checked using the executing PHP binary. -No PHP migration rules are enabled. Review compatibility with the -application's minimum PHP before adopting a preset. +Run `composer standards:check`; apply formatting explicitly with +`composer standards:fix`. Global installation uses +`composer global require fast-forward/code-standards`, then the binary in +`composer global config bin-dir --absolute`. +The GitHub `v0` workflow channel and the Composer package are separate +publications. See the [PHAR distribution contract](docs/phar-distribution.rst) +for the committed binary and CI build contract. A merge does not itself publish a Composer release. +Copyable workflow callers live in [resources/workflows](resources/workflows/README.md). + +Requires PHP ^8.3, Git and the PHP extensions declared in `composer.json`, +including Phar and zlib. Bundled libraries do not enter the consumer's dependency +graph. Executable configurations may load consumer classes; this is no namespace sandbox. +Syntax uses the executing PHP binary. No PHP migration rules are enabled. Workflow guards and their contributor tests additionally require Bash and jq. Hosted Ubuntu runners provide both; the Composer library and CLI use the PHP dependencies declared in this package. @@ -82,12 +95,15 @@ See the [structured check contract](docs/structured-check.rst). Text is the default; `fix` uses text output. Use `--format=json-pretty` for the same report with indentation; see [result presentation](docs/process-contracts.rst#structured-check-presentation). -The default preset stores its ECS cache beneath the system temporary directory, +Source installations store the default ECS cache beneath the system temporary directory, in `fast-forward-code-standards//`. The fingerprint includes the installed preset and owned checker paths and source bytes. Different checkouts cannot invalidate each other's cache, and a changed -owned rule cannot reuse an earlier passing result. Native -`withCache()` overrides remain supported and apply last; share cache directories +owned rule cannot reuse an earlier passing result. The compiled executable keeps +its default cache inside the extracted runtime and removes it at normal shutdown; +separate invocations do not reuse it or accumulate persistent policy directories. +See the [cache lifetime contract](docs/phar-distribution.rst#filesystem-runtime). +Native `withCache()` overrides remain supported and apply last; share cache directories only when their invalidation and concurrent use are intentionally coordinated. `fix` explicitly applies ECS fixes. It does not commit, push or update dependencies. After each successful formatter batch, a read-only check verifies the result. @@ -118,8 +134,6 @@ declare(strict_types=1); use FastForward\CodeStandards\Config\ECSConfig; use Symplify\EasyCodingStandard\Configuration\ECSConfigBuilder; -require __DIR__ . '/vendor/autoload.php'; - return ECSConfig::configure(static function (ECSConfigBuilder $config): void { // Local rule imports/overrides go last; review their semantic risk. $config->withConfiguredRule( @@ -132,10 +146,18 @@ return ECSConfig::configure(static function (ECSConfigBuilder $config): void { Select it explicitly with `--config=ecs.php`. PHP configuration is executable code and belongs in an unprivileged environment. The CLI defaults to its own preset, does not discover or overwrite consumer configuration, and never runs -both ECS and a competing PHP-CS-Fixer pass. Standalone ECS users may import the -same library preset and set their own paths. CLI selection uses the scope below; +both ECS and a competing PHP-CS-Fixer pass. The preset API is available inside +the compiled tool's selected configuration. The package does not expose these +classes through the consumer autoloader. CLI selection uses the scope below; ECS's configured paths do not expand the CLI's explicit selected file list. +To use project rules or rule packages installed in `require-dev`, explicitly load +the project's `vendor/autoload.php` inside the selected configuration before +registering those rules. Composer includes project `autoload` and `autoload-dev` +classes in a development installation. See the +[consumer configuration contract](docs/phar-distribution.rst#consumer-ecs-configuration) +for an example and compatibility limits. + For braced multiline `if`, `elseif`, `while` and `do-while` conditions, the native PSR-12 wrapper places the first expression on the next line and the closing parenthesis on its own line (`) {`, or `);` for do-while). The native @@ -160,10 +182,9 @@ The formatter worker uses ECS's public factory and command APIs with this tool's own Composer installation. An isolated tool does not implicitly load the target checkout's `vendor/autoload.php`. Explicit PHP configuration retains the checkout as `getcwd()`, keeps `__DIR__` and relative paths intact, and may deliberately -require consumer autoloaders or register custom checkers. When the tool is a -development dependency in the consumer's own Composer graph, that installation's -normal binary-proxy bootstrap remains intentional; it is not a separate import -of another checkout's dependency graph. +require consumer autoloaders or register custom checkers. Composer's proxy +includes the compiled executable, which boots its own extracted runtime rather +than loading the consumer autoloader. ## 🔌 Integration and file scope @@ -187,7 +208,7 @@ of another checkout's dependency graph. Start with the [usage guide](docs/index.rst). See [policy and differences](docs/policy.rst), [workflow/ref adoption](docs/automation.rst) and the [consumer skill](resources/skills/fast-forward-code-standards/SKILL.md). The [architecture decision](docs/architecture.rst) defines library/tool dependency -directions and CPX/autoload limits. +directions and executable-configuration limits. The formatter preserves descriptive PHPDoc and inherited documentation. The separate opt-in [`refactor` command](docs/rector.rst) previews an explicit native Rector configuration and requires `--apply` for semantic writes. It is diff --git a/builds/code-standards b/builds/code-standards new file mode 100755 index 0000000..03849b3 Binary files /dev/null and b/builds/code-standards differ diff --git a/composer.json b/composer.json index 58ce7e6..4b2a140 100644 --- a/composer.json +++ b/composer.json @@ -38,6 +38,18 @@ ], "require": { "php": "^8.3", + "ext-filter": "*", + "ext-hash": "*", + "ext-iconv": "*", + "ext-json": "*", + "ext-openssl": "*", + "ext-pcre": "*", + "ext-phar": "*", + "ext-reflection": "*", + "ext-tokenizer": "*", + "ext-zlib": "*" + }, + "require-dev": { "composer-runtime-api": "^2.2", "composer/composer": "^2.10.3", "container-interop/service-provider": "^0.4.1", @@ -45,31 +57,25 @@ "ergebnis/json-normalizer": "^4.11.0", "ergebnis/json-printer": "^3.9.1", "fast-forward/container": "^1.10.1", + "llm/skills": "^1.15", "phpstan/phpstan": "^2.3.3", + "phpunit/phpunit": "^12.5", "povils/phpmnd": "^3.6.1", "psr/container": "^1.1 || ^2.0", "rector/rector": "^2.8.2", "symfony/console": "^7.4 || ^8.0", + "symfony/filesystem": "^7.4 || ^8.0", "symfony/process": "^7.4 || ^8.0", "symplify/easy-coding-standard": "^13.3.8" }, - "require-dev": { - "llm/skills": "^1.15", - "phpunit/phpunit": "^12.5", - "symfony/filesystem": "^7.4 || ^8.0" - }, "suggest": { "ext-dom": "Generate native XML reports for optional magic-number analysis.", "llm/skills": "Optionally synchronize the bundled agent skill in consumer projects." }, "minimum-stability": "stable", - "autoload": { - "psr-4": { - "FastForward\\CodeStandards\\": "src/" - } - }, "autoload-dev": { "psr-4": { + "FastForward\\CodeStandards\\": "src/", "FastForward\\CodeStandards\\Tests\\": "tests/" }, "exclude-from-classmap": [ @@ -77,7 +83,7 @@ ] }, "bin": [ - "bin/code-standards" + "builds/code-standards" ], "config": { "allow-plugins": { @@ -89,11 +95,29 @@ "sort-packages": true }, "extra": { + "phar": { + "runtime-packages": [ + "composer/composer", + "container-interop/service-provider", + "ergebnis/json", + "ergebnis/json-normalizer", + "ergebnis/json-printer", + "fast-forward/container", + "phpstan/phpstan", + "povils/phpmnd", + "psr/container", + "rector/rector", + "symfony/console", + "symfony/process", + "symplify/easy-coding-standard" + ] + }, "skills": { "source": "resources/skills" } }, "scripts": { + "build": "@php -d phar.readonly=0 scripts/phar/build.php", "check": [ "@composer validate --strict", "@composer check-platform-reqs", diff --git a/composer.lock b/composer.lock index 2988ae1..46802ad 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,9 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7fc63af9a2bfd78c7712c47bc60cfbea", - "packages": [ + "content-hash": "35d70d2f5295eebbffc32d28e0a3259f", + "packages": [], + "packages-dev": [ { "name": "brick/varexporter", "version": "0.7.0", @@ -1236,6 +1237,72 @@ ], "time": "2026-10-18T08:00:17+00:00" }, + { + "name": "internal/path", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/php-internal/path.git", + "reference": "e59fc0d530891483b5f47417ccfecb3d02b2218c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-internal/path/zipball/e59fc0d530891483b5f47417ccfecb3d02b2218c", + "reference": "e59fc0d530891483b5f47417ccfecb3d02b2218c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "buggregator/trap": "^1.15", + "infection/infection": "^0.33.2", + "llm/skills": "^1.7", + "roxblnfk/unpoly": "^1.8.1", + "spiral/code-style": "^2.3.1", + "testo/bridge-infection": "^0.1.6", + "testo/testo": "^0.10.21", + "vimeo/psalm": "^7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Internal\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Aleksei Gagarin", + "homepage": "https://github.com/roxblnfk" + } + ], + "description": "Type-safe, immutable file path library with cross-platform support and automatic normalization.", + "keywords": [ + "cross-platform", + "file", + "filepath", + "filesystem", + "helper", + "path", + "unix", + "windows" + ], + "support": { + "issues": "https://github.com/php-internal/path/issues", + "source": "https://github.com/php-internal/path/tree/1.4.0" + }, + "funding": [ + { + "url": "https://boosty.to/roxblnfk", + "type": "boosty" + } + ], + "time": "2026-09-08T10:51:37+00:00" + }, { "name": "justinrainbow/json-schema", "version": "6.14.0", @@ -1428,6 +1495,78 @@ ], "time": "2025-10-11T18:13:12+00:00" }, + { + "name": "llm/skills", + "version": "1.15.0", + "source": { + "type": "git", + "url": "https://github.com/roxblnfk/skills.git", + "reference": "3ee0a545bf3ab77591fd9c35f0317191d78203c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/roxblnfk/skills/zipball/3ee0a545bf3ab77591fd9c35f0317191d78203c5", + "reference": "3ee0a545bf3ab77591fd9c35f0317191d78203c5", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "internal/path": "^1.4", + "php": ">=8.2" + }, + "require-dev": { + "buggregator/trap": "^1.16", + "composer/composer": "^2.10.3", + "infection/infection": "dev-testo-bridge-82 as 0.32.6", + "roxblnfk/unpoly": "^1.8", + "spiral/code-style": "^2.3", + "testo/bridge-infection": "0.1 - 1", + "testo/testo": "0.10 - 1", + "vimeo/psalm": "^7" + }, + "type": "composer-plugin", + "extra": { + "class": "LLM\\Skills\\Composer\\SkillsPlugin" + }, + "autoload": { + "psr-4": { + "LLM\\Skills\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Aleksei Gagarin", + "email": "roxblnfk@gmail.com" + } + ], + "description": "AI skills discovery and management system for LLM agents", + "keywords": [ + "agents", + "ai", + "ai-agents", + "ai-skills", + "claude-code", + "dev", + "llm", + "llm-agents", + "skills" + ], + "support": { + "issues": "https://github.com/roxblnfk/skills/issues", + "source": "https://github.com/roxblnfk/skills" + }, + "funding": [ + { + "url": "https://boosty.to/roxblnfk", + "type": "boosty" + } + ], + "time": "2026-10-15T12:10:05+00:00" + }, { "name": "marc-mabe/php-enum", "version": "v4.7.2", @@ -1501,6 +1640,66 @@ }, "time": "2025-09-14T11:18:39+00:00" }, + { + "name": "myclabs/deep-copy", + "version": "1.14.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2026-08-11T10:17:44+00:00" + }, { "name": "nikic/php-parser", "version": "v5.9.0", @@ -1559,48 +1758,166 @@ "time": "2026-09-13T18:51:52+00:00" }, { - "name": "php-parallel-lint/php-console-color", - "version": "v1.0.1", + "name": "phar-io/manifest", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/php-parallel-lint/PHP-Console-Color.git", - "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88" + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Color/zipball/7adfefd530aa2d7570ba87100a99e2483a543b88", - "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { - "php": ">=5.3.2" - }, - "replace": { - "jakub-onderka/php-console-color": "*" - }, - "require-dev": { - "php-parallel-lint/php-code-style": "^2.0", - "php-parallel-lint/php-parallel-lint": "^1.0", - "php-parallel-lint/php-var-dump-check": "0.*", - "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", - "autoload": { - "psr-4": { - "PHP_Parallel_Lint\\PhpConsoleColor\\": "src/" + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-2-Clause" + "BSD-3-Clause" ], "authors": [ { - "name": "Jakub Onderka", - "email": "jakub.onderka@gmail.com" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" } ], - "description": "Simple library for creating colored console ouput.", + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "php-parallel-lint/php-console-color", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-parallel-lint/PHP-Console-Color.git", + "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Color/zipball/7adfefd530aa2d7570ba87100a99e2483a543b88", + "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "replace": { + "jakub-onderka/php-console-color": "*" + }, + "require-dev": { + "php-parallel-lint/php-code-style": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.0", + "php-parallel-lint/php-var-dump-check": "0.*", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHP_Parallel_Lint\\PhpConsoleColor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com" + } + ], + "description": "Simple library for creating colored console ouput.", "support": { "issues": "https://github.com/php-parallel-lint/PHP-Console-Color/issues", "source": "https://github.com/php-parallel-lint/PHP-Console-Color/tree/v1.0.1" @@ -1724,29 +2041,43 @@ "time": "2026-10-27T14:06:26+00:00" }, { - "name": "phpunit/php-timer", - "version": "8.0.0", + "name": "phpunit/php-code-coverage", + "version": "12.5.8", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7440368d94738d54f2c1de24dbd3871793bbb0dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", - "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7440368d94738d54f2c1de24dbd3871793bbb0dd", + "reference": "7440368d94738d54f2c1de24dbd3871793bbb0dd", "shasum": "" }, "require": { - "php": ">=8.3" + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.9.0", + "php": ">=8.3", + "phpunit/php-text-template": "^5.0.1", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.1.2", + "sebastian/lines-of-code": "^4.0.1", + "sebastian/version": "^6.0", + "theseer/tokenizer": "^2.0.1" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^12.5.37" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "8.0-dev" + "dev-main": "12.5.x-dev" } }, "autoload": { @@ -1765,389 +2096,476 @@ "role": "lead" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ - "timer" + "coverage", + "testing", + "xunit" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.8" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2025-02-07T04:59:38+00:00" + "time": "2026-10-03T14:03:50+00:00" }, { - "name": "povils/phpmnd", - "version": "v3.6.1", + "name": "phpunit/php-file-iterator", + "version": "6.0.2", "source": { "type": "git", - "url": "https://github.com/povils/phpmnd.git", - "reference": "2002af50f627a03d94d01529a3eb02754a23578d" + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a248d1640ab059b075f53a2ef0f9856e864e06b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/povils/phpmnd/zipball/2002af50f627a03d94d01529a3eb02754a23578d", - "reference": "2002af50f627a03d94d01529a3eb02754a23578d", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a248d1640ab059b075f53a2ef0f9856e864e06b5", + "reference": "a248d1640ab059b075f53a2ef0f9856e864e06b5", "shasum": "" }, "require": { - "composer-runtime-api": "^2.0", - "nikic/php-parser": "^4.18 || ^5.0", - "php": "^7.4 || ^8.0", - "php-parallel-lint/php-console-highlighter": "^1.0", - "phpunit/php-timer": "^2.0 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0", - "symfony/console": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0", - "symfony/finder": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.6", - "squizlabs/php_codesniffer": "^2.8.1 || ^3.5" + "phpunit/phpunit": "^12.5.33" }, - "bin": [ - "bin/phpmnd" - ], - "type": "application", - "autoload": { - "psr-4": { - "Povils\\PHPMND\\": "src/" + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Povilas Susinskas", - "email": "povilassusinskas@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "A tool to detect Magic numbers in codebase", + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], "support": { - "issues": "https://github.com/povils/phpmnd/issues", - "source": "https://github.com/povils/phpmnd/tree/v3.6.1" + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.2" }, - "time": "2026-02-21T20:13:10+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-08-25T14:40:53+00:00" }, { - "name": "psr/container", - "version": "2.0.2", + "name": "phpunit/php-invoker", + "version": "6.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "shasum": "" }, "require": { - "php": ">=7.4.0" + "php": ">=8.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-pcntl": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-main": "6.0-dev" } }, "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "process" ], "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" }, - "time": "2021-11-05T16:47:00+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:58+00:00" }, { - "name": "psr/log", - "version": "3.0.2", + "name": "phpunit/php-text-template", + "version": "5.0.1", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "babab73b7ef9fae406f2eb3ee11323a61caa63d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/babab73b7ef9fae406f2eb3ee11323a61caa63d5", + "reference": "babab73b7ef9fae406f2eb3ee11323a61caa63d5", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.37" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-main": "5.0-dev" } }, "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ - "log", - "psr", - "psr-3" + "template" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.1" }, - "time": "2024-09-11T13:17:53+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template", + "type": "tidelift" + } + ], + "time": "2026-10-03T04:51:29+00:00" }, { - "name": "psr/simple-cache", - "version": "3.0.0", + "name": "phpunit/php-timer", + "version": "8.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-main": "8.0-dev" } }, "autoload": { - "psr-4": { - "Psr\\SimpleCache\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Common interfaces for simple caching", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" + "timer" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" }, - "time": "2021-10-29T13:26:27+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:38+00:00" }, { - "name": "react/promise", - "version": "v3.3.0", + "name": "phpunit/phpunit", + "version": "12.5.40", "source": { "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "76bd3198f16aeb375a4d5f3626471445aed0966e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", - "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/76bd3198f16aeb375a4d5f3626471445aed0966e", + "reference": "76bd3198f16aeb375a4d5f3626471445aed0966e", "shasum": "" }, "require": { - "php": ">=7.1.0" - }, - "require-dev": { - "phpstan/phpstan": "1.12.28 || 1.4.10", - "phpunit/phpunit": "^9.6 || ^7.5" + "ext-dom": "*", + "ext-filter": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.14.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.3", + "phpunit/php-code-coverage": "^12.5.8", + "phpunit/php-file-iterator": "^6.0.2", + "phpunit/php-invoker": "^6.0.0", + "phpunit/php-text-template": "^5.0.1", + "phpunit/php-timer": "^8.0.0", + "sebastian/cli-parser": "^4.2.1", + "sebastian/comparator": "^7.1.8", + "sebastian/diff": "^7.0.1", + "sebastian/environment": "^8.1.2", + "sebastian/exporter": "^7.0.3", + "sebastian/global-state": "^8.0.3", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/recursion-context": "^7.0.1", + "sebastian/type": "^6.0.4", + "sebastian/version": "^6.0.1", + "staabm/side-effects-detector": "^1.0.5" }, + "bin": [ + "phpunit" + ], "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.5-dev" + } + }, "autoload": { "files": [ - "src/functions_include.php" + "src/Framework/Assert/Functions.php" ], - "psr-4": { - "React\\Promise\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", "keywords": [ - "promise", - "promises" - ], - "support": { - "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.3.0" + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.40" }, "funding": [ { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" + "url": "https://phpunit.de/sponsoring.html", + "type": "other" } ], - "time": "2025-08-19T18:57:03+00:00" + "time": "2026-10-16T05:55:07+00:00" }, { - "name": "rector/rector", - "version": "2.8.2", + "name": "povils/phpmnd", + "version": "v3.6.1", "source": { "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "03a365d439624872239f22911893c960cc45a6e3" + "url": "https://github.com/povils/phpmnd.git", + "reference": "2002af50f627a03d94d01529a3eb02754a23578d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/03a365d439624872239f22911893c960cc45a6e3", - "reference": "03a365d439624872239f22911893c960cc45a6e3", + "url": "https://api.github.com/repos/povils/phpmnd/zipball/2002af50f627a03d94d01529a3eb02754a23578d", + "reference": "2002af50f627a03d94d01529a3eb02754a23578d", "shasum": "" }, "require": { - "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.3.3" + "composer-runtime-api": "^2.0", + "nikic/php-parser": "^4.18 || ^5.0", + "php": "^7.4 || ^8.0", + "php-parallel-lint/php-console-highlighter": "^1.0", + "phpunit/php-timer": "^2.0 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0", + "symfony/console": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/finder": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0" }, - "conflict": { - "rector/rector-doctrine": "*", - "rector/rector-downgrade-php": "*", - "rector/rector-phpunit": "*", - "rector/rector-symfony": "*" + "require-dev": { + "phpunit/phpunit": "^9.6", + "squizlabs/php_codesniffer": "^2.8.1 || ^3.5" }, "bin": [ - "bin/rector" + "bin/phpmnd" ], - "type": "library", + "type": "application", "autoload": { - "files": [ - "bootstrap.php" - ] + "psr-4": { + "Povils\\PHPMND\\": "src/" + } }, "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.8.2" - }, - "funding": [ + "authors": [ { - "url": "https://github.com/tomasvotruba", - "type": "github" + "name": "Povilas Susinskas", + "email": "povilassusinskas@gmail.com" } ], - "time": "2026-10-28T15:59:43+00:00" + "description": "A tool to detect Magic numbers in codebase", + "support": { + "issues": "https://github.com/povils/phpmnd/issues", + "source": "https://github.com/povils/phpmnd/tree/v3.6.1" + }, + "time": "2026-02-21T20:13:10+00:00" }, { - "name": "seld/jsonlint", - "version": "1.12.1", + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9a90eb5d32d5a500296bf43f946d60246444d5f7", - "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": "^5.3 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.11", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + "php": ">=7.4.0" }, - "bin": [ - "bin/jsonlint" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "psr-4": { - "Seld\\JsonLint\\": "src/Seld/JsonLint/" + "Psr\\Container\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2156,60 +2574,51 @@ ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "https://seld.be" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "JSON Linter", + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "json", - "linter", - "parser", - "validator" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], "support": { - "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.12.1" + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", - "type": "tidelift" - } - ], - "time": "2026-06-12T11:32:29+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { - "name": "seld/phar-utils", - "version": "1.2.3", + "name": "psr/log", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/Seldaek/phar-utils.git", - "reference": "65fb381613fd31745d39a740a112a233f978d2d9" + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/65fb381613fd31745d39a740a112a233f978d2d9", - "reference": "65fb381613fd31745d39a740a112a233f978d2d9", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "php": ">=5.3" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Seld\\PharUtils\\": "src/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2218,54 +2627,48 @@ ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "PHAR file format utilities, for when PHP phars you up", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "phar" + "log", + "psr", + "psr-3" ], "support": { - "issues": "https://github.com/Seldaek/phar-utils/issues", - "source": "https://github.com/Seldaek/phar-utils/tree/1.2.3" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2026-10-01T08:16:23+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { - "name": "seld/signal-handler", - "version": "2.0.2", + "name": "psr/simple-cache", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/Seldaek/signal-handler.git", - "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98" + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", - "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", "shasum": "" }, "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "phpstan/phpstan": "^1", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1", - "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^7.5.20 || ^8.5.23", - "psr/log": "^1 || ^2 || ^3" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { "psr-4": { - "Seld\\Signal\\": "src/" + "Psr\\SimpleCache\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2274,77 +2677,52 @@ ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", + "description": "Common interfaces for simple caching", "keywords": [ - "posix", - "sigint", - "signal", - "sigterm", - "unix" + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" ], "support": { - "issues": "https://github.com/Seldaek/signal-handler/issues", - "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2" + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" }, - "time": "2023-09-03T09:24:00+00:00" + "time": "2021-10-29T13:26:27+00:00" }, { - "name": "symfony/console", - "version": "v7.4.21", + "name": "react/promise", + "version": "v3.3.0", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "21f1bc2296f7ff6043241a8dcbc09559b3732820" + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/21f1bc2296f7ff6043241a8dcbc09559b3732820", - "reference": "21f1bc2296f7ff6043241a8dcbc09559b3732820", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2|^8.0" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" + "php": ">=7.1.0" }, "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/lock": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "React\\Promise\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2352,1368 +2730,740 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", + "description": "A lightweight implementation of CommonJS Promises/A for PHP", "keywords": [ - "cli", - "command-line", - "console", - "terminal" + "promise", + "promises" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.21" + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.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" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2026-10-12T18:45:38+00:00" + "time": "2025-08-19T18:57:03+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.7.1", + "name": "rector/rector", + "version": "2.8.2", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + "url": "https://github.com/rectorphp/rector.git", + "reference": "03a365d439624872239f22911893c960cc45a6e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", - "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/03a365d439624872239f22911893c960cc45a6e3", + "reference": "03a365d439624872239f22911893c960cc45a6e3", "shasum": "" }, "require": { - "php": ">=8.1" + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.3.3" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" - } + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" }, + "bin": [ + "bin/rector" + ], + "type": "library", "autoload": { "files": [ - "function.php" + "bootstrap.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": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.8.2" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", + "url": "https://github.com/tomasvotruba", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2026-06-05T06:23:12+00:00" + "time": "2026-10-28T15:59:43+00:00" }, { - "name": "symfony/filesystem", - "version": "v7.4.21", + "name": "sebastian/cli-parser", + "version": "4.2.1", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "fd0e3462be6d32a0d130e8423e4940a7688162d8" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "7d05781b13f7dec9043a629a21d086ed74582a15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/fd0e3462be6d32a0d130e8423e4940a7688162d8", - "reference": "fd0e3462be6d32a0d130e8423e4940a7688162d8", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/7d05781b13f7dec9043a629a21d086ed74582a15", + "reference": "7d05781b13f7dec9043a629a21d086ed74582a15", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" + "php": ">=8.3" }, "require-dev": { - "symfony/process": "^6.4|^7.0|^8.0" + "phpunit/phpunit": "^12.5.25" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.2-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.21" + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.1" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" + "url": "https://github.com/sebastianbergmann", + "type": "github" }, { - "url": "https://github.com/fabpot", - "type": "github" + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" }, { - "url": "https://github.com/nicolas-grekas", - "type": "github" + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", "type": "tidelift" } ], - "time": "2026-10-06T20:05:19+00:00" + "time": "2026-05-17T05:29:34+00:00" }, { - "name": "symfony/finder", - "version": "v7.4.20", + "name": "sebastian/comparator", + "version": "7.1.8", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e" + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "7c65c1e79836812819705b473a90c12399542485" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", - "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/7c65c1e79836812819705b473a90c12399542485", + "reference": "7c65c1e79836812819705b473a90c12399542485", "shasum": "" }, "require": { - "php": ">=8.2" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0.3" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0|^8.0" + "phpunit/phpunit": "^12.5.25" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.1-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.20" - }, - "funding": [ + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, { - "url": "https://symfony.com/sponsor", - "type": "custom" + "name": "Volker Dusch", + "email": "github@wallbash.com" }, { - "url": "https://github.com/fabpot", + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", "type": "github" }, { - "url": "https://github.com/nicolas-grekas", - "type": "github" + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", "type": "tidelift" } ], - "time": "2026-09-26T06:31:10+00:00" + "time": "2026-05-21T04:45:25+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.37.0", + "name": "sebastian/complexity", + "version": "5.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", "shasum": "" }, "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" + "nikic/php-parser": "^5.0", + "php": ">=8.3" }, - "suggest": { - "ext-ctype": "For best performance" + "require-dev": { + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "branch-alias": { + "dev-main": "5.0-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2026-04-10T16:19:22+00:00" + "time": "2025-02-07T04:55:25+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.43.0", + "name": "sebastian/diff", + "version": "7.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "5cac8f4db97208823aa353f59a01faad2da52c75" + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5cac8f4db97208823aa353f59a01faad2da52c75", - "reference": "5cac8f4db97208823aa353f59a01faad2da52c75", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6", + "reference": "cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.3" }, - "suggest": { - "ext-intl": "For best performance" + "require-dev": { + "phpunit/phpunit": "^12.5.33", + "symfony/process": "^7.4.17" }, "type": "library", "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "branch-alias": { + "dev-main": "7.0-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" + "diff", + "udiff", + "unidiff", + "unified diff" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.0" + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.1" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" + "url": "https://github.com/sebastianbergmann", + "type": "github" }, { - "url": "https://github.com/fabpot", - "type": "github" + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" }, { - "url": "https://github.com/nicolas-grekas", - "type": "github" + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/sebastian/diff", "type": "tidelift" } ], - "time": "2026-09-25T15:46:47+00:00" + "time": "2026-08-25T15:35:54+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.43.0", + "name": "sebastian/environment", + "version": "8.1.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b" + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/ebd57ccc7edb93d632e184efbe7693d2517bab3b", - "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/9d32c685773823b1983e256ae4ecd48a10d6e439", + "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.26" }, "suggest": { - "ext-intl": "For best performance" + "ext-posix": "*" }, "type": "library", "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "branch-alias": { + "dev-main": "8.1-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, "classmap": [ - "Resources/stubs" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" + "Xdebug", + "environment", + "hhvm" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.43.0" + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/8.1.2" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" + "url": "https://github.com/sebastianbergmann", + "type": "github" }, { - "url": "https://github.com/fabpot", - "type": "github" + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" }, { - "url": "https://github.com/nicolas-grekas", - "type": "github" + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", "type": "tidelift" } ], - "time": "2026-09-23T22:02:09+00:00" + "time": "2026-05-25T13:40:20+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.43.0", + "name": "sebastian/exporter", + "version": "7.0.3", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72" + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", - "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", + "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", "shasum": "" }, "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/recursion-context": "^7.0.1" }, - "suggest": { - "ext-mbstring": "For best performance" + "require-dev": { + "phpunit/phpunit": "^12.5.25" }, "type": "library", "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "branch-alias": { + "dev-main": "7.0-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" + "export", + "exporter" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.0" + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.3" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" + "url": "https://github.com/sebastianbergmann", + "type": "github" }, { - "url": "https://github.com/fabpot", - "type": "github" + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" }, { - "url": "https://github.com/nicolas-grekas", - "type": "github" + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", "type": "tidelift" } ], - "time": "2026-09-24T15:19:04+00:00" + "time": "2026-05-20T04:37:17+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.37.0", + "name": "sebastian/global-state", + "version": "8.0.3", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", - "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b164d3274d6537ab462591c5755f76a8f5b1aae9", + "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0.1" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^12.5.28" }, "type": "library", "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "branch-alias": { + "dev-main": "8.0-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, "classmap": [ - "Resources/stubs" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "global state" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.37.0" + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.3" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" + "url": "https://github.com/sebastianbergmann", + "type": "github" }, { - "url": "https://github.com/fabpot", - "type": "github" + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" }, { - "url": "https://github.com/nicolas-grekas", - "type": "github" + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-06-01T15:10:33+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.43.0", + "name": "sebastian/lines-of-code", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74" + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", - "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d543b8ef219dcd8da262cbb958639a96bedba10e", + "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e", "shasum": "" }, "require": { - "php": ">=7.2" + "nikic/php-parser": "^5.7.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" }, "type": "library", "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "branch-alias": { + "dev-main": "4.0-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, "classmap": [ - "Resources/stubs" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.43.0" + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.1" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" + "url": "https://github.com/sebastianbergmann", + "type": "github" }, { - "url": "https://github.com/fabpot", - "type": "github" + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" }, { - "url": "https://github.com/nicolas-grekas", - "type": "github" + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code", "type": "tidelift" } ], - "time": "2026-09-25T15:50:05+00:00" + "time": "2026-05-19T16:22:07+00:00" }, { - "name": "symfony/polyfill-php81", - "version": "v1.38.1", + "name": "sebastian/object-enumerator", + "version": "7.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", - "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" - }, - "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 backporting some PHP 8.1+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.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": "2026-05-26T12:45:58+00:00" - }, - { - "name": "symfony/polyfill-php84", - "version": "v1.43.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/22c6548499cbe5c5bb072fc0c42312dff062a015", - "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php84\\": "" - }, - "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 backporting some PHP 8.4+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.43.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": "2026-09-23T22:01:09+00:00" - }, - { - "name": "symfony/process", - "version": "v7.4.21", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "e5247f4a5e0797d232ad8e8e6803aeb341843c83" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/e5247f4a5e0797d232ad8e8e6803aeb341843c83", - "reference": "e5247f4a5e0797d232ad8e8e6803aeb341843c83", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "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": "Executes commands in sub-processes", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v7.4.21" - }, - "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": "2026-10-08T16:56:12+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v3.7.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", - "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", - "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.7-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.7.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-07-27T15:39:01+00:00" - }, - { - "name": "symfony/string", - "version": "v7.4.21", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "9bc35d074ad16a58c21cd52b18c5cacc4f4c3981" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/9bc35d074ad16a58c21cd52b18c5cacc4f4c3981", - "reference": "9bc35d074ad16a58c21cd52b18c5cacc4f4c3981", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "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.1|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0|^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/v7.4.21" - }, - "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": "2026-10-03T15:15:30+00:00" - }, - { - "name": "symplify/easy-coding-standard", - "version": "13.3.9", - "source": { - "type": "git", - "url": "https://github.com/ecsphp/ecs.git", - "reference": "c4c68c81775e8227dd01c064776edd46975d9d86" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ecsphp/ecs/zipball/c4c68c81775e8227dd01c064776edd46975d9d86", - "reference": "c4c68c81775e8227dd01c064776edd46975d9d86", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "conflict": { - "friendsofphp/php-cs-fixer": "<3.95.20", - "phpcsstandards/php_codesniffer": "<4.0.4", - "symplify/coding-standard": "<13.0" - }, - "bin": [ - "bin/ecs" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer", - "keywords": [ - "Code style", - "automation", - "fixer", - "static analysis" - ], - "support": { - "issues": "https://github.com/ecsphp/ecs/issues", - "source": "https://github.com/ecsphp/ecs/tree/13.3.9" - }, - "time": "2026-10-23T12:59:45+00:00" - }, - { - "name": "webimpress/safe-writer", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/webimpress/safe-writer.git", - "reference": "9d37cc8bee20f7cb2f58f6e23e05097eab5072e6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webimpress/safe-writer/zipball/9d37cc8bee20f7cb2f58f6e23e05097eab5072e6", - "reference": "9d37cc8bee20f7cb2f58f6e23e05097eab5072e6", - "shasum": "" - }, - "require": { - "php": "^7.3 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.5.4", - "vimeo/psalm": "^4.7", - "webimpress/coding-standard": "^1.2.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.2.x-dev", - "dev-develop": "2.3.x-dev", - "dev-release-1.0": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Webimpress\\SafeWriter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "description": "Tool to write files safely, to avoid race conditions", - "keywords": [ - "concurrent write", - "file writer", - "race condition", - "safe writer", - "webimpress" - ], - "support": { - "issues": "https://github.com/webimpress/safe-writer/issues", - "source": "https://github.com/webimpress/safe-writer/tree/2.2.0" - }, - "funding": [ - { - "url": "https://github.com/michalbundyra", - "type": "github" - } - ], - "time": "2021-04-19T16:34:45+00:00" - } - ], - "packages-dev": [ - { - "name": "internal/path", - "version": "1.4.0", - "source": { - "type": "git", - "url": "https://github.com/php-internal/path.git", - "reference": "e59fc0d530891483b5f47417ccfecb3d02b2218c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-internal/path/zipball/e59fc0d530891483b5f47417ccfecb3d02b2218c", - "reference": "e59fc0d530891483b5f47417ccfecb3d02b2218c", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "buggregator/trap": "^1.15", - "infection/infection": "^0.33.2", - "llm/skills": "^1.7", - "roxblnfk/unpoly": "^1.8.1", - "spiral/code-style": "^2.3.1", - "testo/bridge-infection": "^0.1.6", - "testo/testo": "^0.10.21", - "vimeo/psalm": "^7" - }, - "type": "library", - "autoload": { - "psr-4": { - "Internal\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Aleksei Gagarin", - "homepage": "https://github.com/roxblnfk" - } - ], - "description": "Type-safe, immutable file path library with cross-platform support and automatic normalization.", - "keywords": [ - "cross-platform", - "file", - "filepath", - "filesystem", - "helper", - "path", - "unix", - "windows" - ], - "support": { - "issues": "https://github.com/php-internal/path/issues", - "source": "https://github.com/php-internal/path/tree/1.4.0" - }, - "funding": [ - { - "url": "https://boosty.to/roxblnfk", - "type": "boosty" - } - ], - "time": "2026-09-08T10:51:37+00:00" - }, - { - "name": "llm/skills", - "version": "1.15.0", - "source": { - "type": "git", - "url": "https://github.com/roxblnfk/skills.git", - "reference": "3ee0a545bf3ab77591fd9c35f0317191d78203c5" + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/roxblnfk/skills/zipball/3ee0a545bf3ab77591fd9c35f0317191d78203c5", - "reference": "3ee0a545bf3ab77591fd9c35f0317191d78203c5", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", "shasum": "" }, "require": { - "composer-plugin-api": "^2.0", - "internal/path": "^1.4", - "php": ">=8.2" + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" }, "require-dev": { - "buggregator/trap": "^1.16", - "composer/composer": "^2.10.3", - "infection/infection": "dev-testo-bridge-82 as 0.32.6", - "roxblnfk/unpoly": "^1.8", - "spiral/code-style": "^2.3", - "testo/bridge-infection": "0.1 - 1", - "testo/testo": "0.10 - 1", - "vimeo/psalm": "^7" + "phpunit/phpunit": "^12.0" }, - "type": "composer-plugin", + "type": "library", "extra": { - "class": "LLM\\Skills\\Composer\\SkillsPlugin" + "branch-alias": { + "dev-main": "7.0-dev" + } }, "autoload": { - "psr-4": { - "LLM\\Skills\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3721,120 +3471,105 @@ ], "authors": [ { - "name": "Aleksei Gagarin", - "email": "roxblnfk@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "AI skills discovery and management system for LLM agents", - "keywords": [ - "agents", - "ai", - "ai-agents", - "ai-skills", - "claude-code", - "dev", - "llm", - "llm-agents", - "skills" - ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { - "issues": "https://github.com/roxblnfk/skills/issues", - "source": "https://github.com/roxblnfk/skills" + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" }, "funding": [ { - "url": "https://boosty.to/roxblnfk", - "type": "boosty" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "time": "2026-10-15T12:10:05+00:00" + "time": "2025-02-07T04:57:48+00:00" }, { - "name": "myclabs/deep-copy", - "version": "1.14.0", + "name": "sebastian/object-reflector", + "version": "5.0.0", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", - "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a", "shasum": "" }, "require": { - "php": "^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" + "php": ">=8.3" }, "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + "phpunit/phpunit": "^12.0" }, "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" }, "funding": [ { - "url": "https://github.com/mnapoli", + "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2026-08-11T10:17:44+00:00" + "time": "2025-02-07T04:58:17+00:00" }, { - "name": "phar-io/manifest", - "version": "2.0.4", + "name": "sebastian/recursion-context", + "version": "7.0.1", "source": { "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -3848,52 +3583,71 @@ ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "name": "Adam Harvey", + "email": "aharvey@php.net" } ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" }, "funding": [ { - "url": "https://github.com/theseer", + "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2024-03-03T12:33:53+00:00" + "time": "2025-08-13T04:44:59+00:00" }, { - "name": "phar-io/version", - "version": "3.2.1", + "name": "sebastian/type", + "version": "6.0.4", "source": { "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "82ff822c2edc46724be9f7411d3163021f602773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/82ff822c2edc46724be9f7411d3163021f602773", + "reference": "82ff822c2edc46724be9f7411d3163021f602773", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -3904,67 +3658,60 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", - "role": "Developer" + "role": "lead" } ], - "description": "Library for handling version information and constraints", + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/6.0.4" }, - "time": "2022-02-21T01:04:05+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" + } + ], + "time": "2026-05-20T06:45:45+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "12.5.8", + "name": "sebastian/version", + "version": "6.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "7440368d94738d54f2c1de24dbd3871793bbb0dd" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "9e935a16187e14f39b0e79910dabd614972cdb26" }, "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7440368d94738d54f2c1de24dbd3871793bbb0dd", - "reference": "7440368d94738d54f2c1de24dbd3871793bbb0dd", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^5.9.0", - "php": ">=8.3", - "phpunit/php-text-template": "^5.0.1", - "sebastian/complexity": "^5.0", - "sebastian/environment": "^8.1.2", - "sebastian/lines-of-code": "^4.0.1", - "sebastian/version": "^6.0", - "theseer/tokenizer": "^2.0.1" - }, - "require-dev": { - "phpunit/phpunit": "^12.5.37" + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/9e935a16187e14f39b0e79910dabd614972cdb26", + "reference": "9e935a16187e14f39b0e79910dabd614972cdb26", + "shasum": "" }, - "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "require": { + "php": ">=8.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "12.5.x-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -3983,17 +3730,12 @@ "role": "lead" } ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.8" + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/6.0.1" }, "funding": [ { @@ -4009,1295 +3751,1493 @@ "type": "thanks_dev" }, { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "url": "https://tidelift.com/funding/github/packagist/sebastian/version", "type": "tidelift" } ], - "time": "2026-10-03T14:03:50+00:00" + "time": "2026-10-04T05:50:08+00:00" }, { - "name": "phpunit/php-file-iterator", - "version": "6.0.2", + "name": "seld/jsonlint", + "version": "1.12.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a248d1640ab059b075f53a2ef0f9856e864e06b5" + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a248d1640ab059b075f53a2ef0f9856e864e06b5", - "reference": "a248d1640ab059b075f53a2ef0f9856e864e06b5", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9a90eb5d32d5a500296bf43f946d60246444d5f7", + "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7", "shasum": "" }, "require": { - "php": ">=8.3" + "php": "^5.3 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^12.5.33" + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" }, + "bin": [ + "bin/jsonlint" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" } ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "description": "JSON Linter", "keywords": [ - "filesystem", - "iterator" + "json", + "linter", + "parser", + "validator" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.2" + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.12.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/Seldaek", "type": "github" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", "type": "tidelift" } ], - "time": "2026-08-25T14:40:53+00:00" + "time": "2026-06-12T11:32:29+00:00" }, { - "name": "phpunit/php-invoker", - "version": "6.0.0", + "name": "seld/phar-utils", + "version": "1.2.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "65fb381613fd31745d39a740a112a233f978d2d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", - "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/65fb381613fd31745d39a740a112a233f978d2d9", + "reference": "65fb381613fd31745d39a740a112a233f978d2d9", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=5.3" }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^12.0" + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } }, - "suggest": { - "ext-pcntl": "*" + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.3" + }, + "time": "2026-10-01T08:16:23+00:00" + }, + { + "name": "seld/signal-handler", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/signal-handler.git", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "phpstan/phpstan": "^1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", + "psr/log": "^1 || ^2 || ^3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "2.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Seld\\Signal\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", "keywords": [ - "process" + "posix", + "sigint", + "signal", + "sigterm", + "unix" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" + "issues": "https://github.com/Seldaek/signal-handler/issues", + "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2" + }, + "time": "2023-09-03T09:24:00+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/staabm", "type": "github" } ], - "time": "2025-02-07T04:58:58+00:00" + "time": "2024-10-20T05:08:20+00:00" }, { - "name": "phpunit/php-text-template", - "version": "5.0.1", + "name": "symfony/console", + "version": "v7.4.21", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "babab73b7ef9fae406f2eb3ee11323a61caa63d5" + "url": "https://github.com/symfony/console.git", + "reference": "21f1bc2296f7ff6043241a8dcbc09559b3732820" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/babab73b7ef9fae406f2eb3ee11323a61caa63d5", - "reference": "babab73b7ef9fae406f2eb3ee11323a61caa63d5", + "url": "https://api.github.com/repos/symfony/console/zipball/21f1bc2296f7ff6043241a8dcbc09559b3732820", + "reference": "21f1bc2296f7ff6043241a8dcbc09559b3732820", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "phpunit/phpunit": "^12.5.37" + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", "keywords": [ - "template" + "cli", + "command-line", + "console", + "terminal" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.1" + "source": "https://github.com/symfony/console/tree/v7.4.21" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-10-03T04:51:29+00:00" + "time": "2026-10-12T18:45:38+00:00" }, { - "name": "phpunit/phpunit", - "version": "12.5.40", + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "76bd3198f16aeb375a4d5f3626471445aed0966e" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/76bd3198f16aeb375a4d5f3626471445aed0966e", - "reference": "76bd3198f16aeb375a4d5f3626471445aed0966e", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-filter": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.14.0", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=8.3", - "phpunit/php-code-coverage": "^12.5.8", - "phpunit/php-file-iterator": "^6.0.2", - "phpunit/php-invoker": "^6.0.0", - "phpunit/php-text-template": "^5.0.1", - "phpunit/php-timer": "^8.0.0", - "sebastian/cli-parser": "^4.2.1", - "sebastian/comparator": "^7.1.8", - "sebastian/diff": "^7.0.1", - "sebastian/environment": "^8.1.2", - "sebastian/exporter": "^7.0.3", - "sebastian/global-state": "^8.0.3", - "sebastian/object-enumerator": "^7.0.0", - "sebastian/recursion-context": "^7.0.1", - "sebastian/type": "^6.0.4", - "sebastian/version": "^6.0.1", - "staabm/side-effects-detector": "^1.0.5" + "php": ">=8.1" }, - "bin": [ - "phpunit" - ], "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-main": "12.5-dev" + "dev-main": "3.7-dev" } }, "autoload": { "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" + "function.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.40" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { - "url": "https://phpunit.de/sponsoring.html", - "type": "other" + "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": "2026-10-16T05:55:07+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { - "name": "sebastian/cli-parser", - "version": "4.2.1", + "name": "symfony/filesystem", + "version": "v7.4.21", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "7d05781b13f7dec9043a629a21d086ed74582a15" + "url": "https://github.com/symfony/filesystem.git", + "reference": "fd0e3462be6d32a0d130e8423e4940a7688162d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/7d05781b13f7dec9043a629a21d086ed74582a15", - "reference": "7d05781b13f7dec9043a629a21d086ed74582a15", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/fd0e3462be6d32a0d130e8423e4940a7688162d8", + "reference": "fd0e3462be6d32a0d130e8423e4940a7688162d8", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "phpunit/phpunit": "^12.5.25" + "symfony/process": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.2-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.1" + "source": "https://github.com/symfony/filesystem/tree/v7.4.21" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-05-17T05:29:34+00:00" + "time": "2026-10-06T20:05:19+00:00" }, { - "name": "sebastian/comparator", - "version": "7.1.8", + "name": "symfony/finder", + "version": "v7.4.20", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "7c65c1e79836812819705b473a90c12399542485" + "url": "https://github.com/symfony/finder.git", + "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/7c65c1e79836812819705b473a90c12399542485", - "reference": "7c65c1e79836812819705b473a90c12399542485", + "url": "https://api.github.com/repos/symfony/finder/zipball/2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", + "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.3", - "sebastian/diff": "^7.0", - "sebastian/exporter": "^7.0.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^12.5.25" - }, - "suggest": { - "ext-bcmath": "For comparing BcMath\\Number objects" + "symfony/filesystem": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.1-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.8" + "source": "https://github.com/symfony/finder/tree/v7.4.20" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-05-21T04:45:25+00:00" + "time": "2026-09-26T06:31:10+00:00" }, { - "name": "sebastian/complexity", - "version": "5.0.0", + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", - "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { - "nikic/php-parser": "^5.0", - "php": ">=8.3" + "php": ">=7.2" }, - "require-dev": { - "phpunit/phpunit": "^12.0" + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "5.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "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-02-07T04:55:25+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { - "name": "sebastian/diff", - "version": "7.0.1", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "5cac8f4db97208823aa353f59a01faad2da52c75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6", - "reference": "cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5cac8f4db97208823aa353f59a01faad2da52c75", + "reference": "5cac8f4db97208823aa353f59a01faad2da52c75", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=7.2" }, - "require-dev": { - "phpunit/phpunit": "^12.5.33", - "symfony/process": "^7.4.17" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "7.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/7.0.1" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/diff", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-08-25T15:35:54+00:00" + "time": "2026-09-25T15:46:47+00:00" }, { - "name": "sebastian/environment", - "version": "8.1.2", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/9d32c685773823b1983e256ae4ecd48a10d6e439", - "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/ebd57ccc7edb93d632e184efbe7693d2517bab3b", + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b", "shasum": "" }, "require": { - "php": ">=8.3" - }, - "require-dev": { - "phpunit/phpunit": "^12.5.26" + "php": ">=7.2" }, "suggest": { - "ext-posix": "*" + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "8.1-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, "classmap": [ - "src/" + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", "keywords": [ - "Xdebug", - "environment", - "hhvm" + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/8.1.2" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.43.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-05-25T13:40:20+00:00" + "time": "2026-09-23T22:02:09+00:00" }, { - "name": "sebastian/exporter", - "version": "7.0.3", + "name": "symfony/polyfill-mbstring", + "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", - "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": ">=8.3", - "sebastian/recursion-context": "^7.0.1" + "ext-iconv": "*", + "php": ">=7.2" }, - "require-dev": { - "phpunit/phpunit": "^12.5.25" + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "7.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", "keywords": [ - "export", - "exporter" + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.3" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-05-20T04:37:17+00:00" + "time": "2026-09-24T15:19:04+00:00" }, { - "name": "sebastian/global-state", - "version": "8.0.3", + "name": "symfony/polyfill-php73", + "version": "v1.37.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b164d3274d6537ab462591c5755f76a8f5b1aae9", - "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "shasum": "" }, "require": { - "php": ">=8.3", - "sebastian/object-reflector": "^5.0", - "sebastian/recursion-context": "^7.0.1" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^12.5.28" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "8.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, "classmap": [ - "src/" + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", "keywords": [ - "global state" + "compatibility", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.3" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.37.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-06-01T15:10:33+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "sebastian/lines-of-code", - "version": "4.0.1", + "name": "symfony/polyfill-php80", + "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d543b8ef219dcd8da262cbb958639a96bedba10e", - "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", "shasum": "" }, "require": { - "nikic/php-parser": "^5.7.0", - "php": ">=8.3" - }, - "require-dev": { - "phpunit/phpunit": "^12.5.25" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "4.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, "classmap": [ - "src/" + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.1" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.43.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-05-19T16:22:07+00:00" + "time": "2026-09-25T15:50:05+00:00" }, { - "name": "sebastian/object-enumerator", - "version": "7.0.0", + "name": "symfony/polyfill-php81", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", - "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", "shasum": "" }, "require": { - "php": ">=8.3", - "sebastian/object-reflector": "^5.0", - "sebastian/recursion-context": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^12.0" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "7.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, "classmap": [ - "src/" + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "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-02-07T04:57:48+00:00" + "time": "2026-05-26T12:45:58+00:00" }, { - "name": "sebastian/object-reflector", - "version": "5.0.0", + "name": "symfony/polyfill-php84", + "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "4bfa827c969c98be1e527abd576533293c634f6a" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", - "reference": "4bfa827c969c98be1e527abd576533293c634f6a", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/22c6548499cbe5c5bb072fc0c42312dff062a015", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015", "shasum": "" }, "require": { - "php": ">=8.3" - }, - "require-dev": { - "phpunit/phpunit": "^12.0" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "5.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, "classmap": [ - "src/" + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.43.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "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-02-07T04:58:17+00:00" + "time": "2026-09-23T22:01:09+00:00" }, { - "name": "sebastian/recursion-context", - "version": "7.0.1", + "name": "symfony/process", + "version": "v7.4.21", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" + "url": "https://github.com/symfony/process.git", + "reference": "e5247f4a5e0797d232ad8e8e6803aeb341843c83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", - "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "url": "https://api.github.com/repos/symfony/process/zipball/e5247f4a5e0797d232ad8e8e6803aeb341843c83", + "reference": "e5247f4a5e0797d232ad8e8e6803aeb341843c83", "shasum": "" }, "require": { - "php": ">=8.3" - }, - "require-dev": { - "phpunit/phpunit": "^12.0" + "php": ">=8.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Adam Harvey", - "email": "aharvey@php.net" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" + "source": "https://github.com/symfony/process/tree/v7.4.21" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-08-13T04:44:59+00:00" + "time": "2026-10-08T16:56:12+00:00" }, { - "name": "sebastian/type", - "version": "6.0.4", + "name": "symfony/service-contracts", + "version": "v3.7.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "82ff822c2edc46724be9f7411d3163021f602773" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/82ff822c2edc46724be9f7411d3163021f602773", - "reference": "82ff822c2edc46724be9f7411d3163021f602773", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", "shasum": "" }, "require": { - "php": ">=8.3" - }, - "require-dev": { - "phpunit/phpunit": "^12.5.25" + "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": "6.0-dev" + "dev-main": "3.7-dev" } }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/6.0.4" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.3" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-05-20T06:45:45+00:00" + "time": "2026-07-27T15:39:01+00:00" }, { - "name": "sebastian/version", - "version": "6.0.1", + "name": "symfony/string", + "version": "v7.4.21", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "9e935a16187e14f39b0e79910dabd614972cdb26" + "url": "https://github.com/symfony/string.git", + "reference": "9bc35d074ad16a58c21cd52b18c5cacc4f4c3981" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/9e935a16187e14f39b0e79910dabd614972cdb26", - "reference": "9e935a16187e14f39b0e79910dabd614972cdb26", + "url": "https://api.github.com/repos/symfony/string/zipball/9bc35d074ad16a58c21cd52b18c5cacc4f4c3981", + "reference": "9bc35d074ad16a58c21cd52b18c5cacc4f4c3981", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, + "type": "library", "autoload": { - "classmap": [ - "src/" + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", + "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": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/6.0.1" + "source": "https://github.com/symfony/string/tree/v7.4.21" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/version", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-10-04T05:50:08+00:00" + "time": "2026-10-03T15:15:30+00:00" }, { - "name": "staabm/side-effects-detector", - "version": "1.0.5", + "name": "symplify/easy-coding-standard", + "version": "13.3.9", "source": { "type": "git", - "url": "https://github.com/staabm/side-effects-detector.git", - "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + "url": "https://github.com/ecsphp/ecs.git", + "reference": "c4c68c81775e8227dd01c064776edd46975d9d86" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", - "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "url": "https://api.github.com/repos/ecsphp/ecs/zipball/c4c68c81775e8227dd01c064776edd46975d9d86", + "reference": "c4c68c81775e8227dd01c064776edd46975d9d86", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" + "php": ">=7.2" }, - "require-dev": { - "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^1.12.6", - "phpunit/phpunit": "^9.6.21", - "symfony/var-dumper": "^5.4.43", - "tomasvotruba/type-coverage": "1.0.0", - "tomasvotruba/unused-public": "1.0.0" + "conflict": { + "friendsofphp/php-cs-fixer": "<3.95.20", + "phpcsstandards/php_codesniffer": "<4.0.4", + "symplify/coding-standard": "<13.0" }, + "bin": [ + "bin/ecs" + ], "type": "library", "autoload": { - "classmap": [ - "lib/" + "files": [ + "bootstrap.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "A static analysis tool to detect side effects in PHP code", + "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer", "keywords": [ + "Code style", + "automation", + "fixer", "static analysis" ], "support": { - "issues": "https://github.com/staabm/side-effects-detector/issues", - "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + "issues": "https://github.com/ecsphp/ecs/issues", + "source": "https://github.com/ecsphp/ecs/tree/13.3.9" }, - "funding": [ - { - "url": "https://github.com/staabm", - "type": "github" - } - ], - "time": "2024-10-20T05:08:20+00:00" + "time": "2026-10-23T12:59:45+00:00" }, { "name": "theseer/tokenizer", @@ -5348,6 +5288,65 @@ } ], "time": "2025-12-08T11:19:18+00:00" + }, + { + "name": "webimpress/safe-writer", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/webimpress/safe-writer.git", + "reference": "9d37cc8bee20f7cb2f58f6e23e05097eab5072e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webimpress/safe-writer/zipball/9d37cc8bee20f7cb2f58f6e23e05097eab5072e6", + "reference": "9d37cc8bee20f7cb2f58f6e23e05097eab5072e6", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5.4", + "vimeo/psalm": "^4.7", + "webimpress/coding-standard": "^1.2.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev", + "dev-develop": "2.3.x-dev", + "dev-release-1.0": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Webimpress\\SafeWriter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "description": "Tool to write files safely, to avoid race conditions", + "keywords": [ + "concurrent write", + "file writer", + "race condition", + "safe writer", + "webimpress" + ], + "support": { + "issues": "https://github.com/webimpress/safe-writer/issues", + "source": "https://github.com/webimpress/safe-writer/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2021-04-19T16:34:45+00:00" } ], "aliases": [], @@ -5357,9 +5356,20 @@ "prefer-lowest": false, "platform": { "php": "^8.3", + "ext-filter": "*", + "ext-hash": "*", + "ext-iconv": "*", + "ext-json": "*", + "ext-openssl": "*", + "ext-pcre": "*", + "ext-phar": "*", + "ext-reflection": "*", + "ext-tokenizer": "*", + "ext-zlib": "*" + }, + "platform-dev": { "composer-runtime-api": "^2.2" }, - "platform-dev": {}, "platform-overrides": { "php": "8.3.0" }, diff --git a/docs/api/configuration.rst b/docs/api/configuration.rst index 64e8ea2..c4834e1 100644 --- a/docs/api/configuration.rst +++ b/docs/api/configuration.rst @@ -21,8 +21,6 @@ configuration trust and execution contracts are documented in the use PhpCsFixer\Fixer\Phpdoc\PhpdocAlignFixer; use Symplify\EasyCodingStandard\Configuration\ECSConfigBuilder; - require __DIR__ . '/vendor/autoload.php'; - return ECSConfig::configure(static function (ECSConfigBuilder $config): void { $config->withConfiguredRule(PhpdocAlignFixer::class, ['align' => 'left']); }); @@ -31,6 +29,8 @@ Save the example as ``ecs.php`` in a disposable consumer, then select it with ``--config=ecs.php``. A configuration file is executable PHP and must be trusted at the privilege level of its process. Invalid paths are rejected before ECS runs; native configuration errors propagate from ECS. +The compiled CLI supplies these classes to selected configuration; installing +the PHAR does not export them through the consumer Composer autoloader. Internal services ----------------- diff --git a/docs/architecture.rst b/docs/architecture.rst index 1886b43..aca64e0 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -13,19 +13,18 @@ Decision Libraries expose reusable runtime behavior: Reflection, Console, Iterators and Enum are examples of library responsibilities. Tools own executable development workflows: Changelog, Code Standards and Testing are tool responsibilities. These are dependency roles, not a request to create Reflection or Console now. -Tools may depend on libraries at runtime. Libraries may consume tools as development dependencies. A library must not require a development tool for its normal runtime behavior. Tools must not depend on DevTools in production or create a circular tool/bootstrap prerequisite. Code Standards provides its focused preset as a library API for tool consumers while remaining a development tool; that API does not make application tests/reports part of its domain. ``require-dev`` dependencies are not transitively installed for library consumers: an application explicitly selects the development tools it actually uses. - -CPX separates installation and dependency resolution. It does not isolate PHP namespaces when PHPUnit, Rector or executable configuration loads the consumer and tool in the same process. Version conflicts and global state still require explicit compatibility tests or a real process boundary. A separate Composer graph alone must never be described as runtime isolation. Analyzer/autoload fixtures must prove that the code under analysis comes from the consumer checkout, rather than another copy installed inside the tool. Syntax parsing uses ``php -n -l`` without application autoload. The formatter worker loads its own Composer runtime and uses ECS's public container factory and native check command. It does not invoke ECS's CLI bootstrap, which otherwise imports the working directory's vendor autoloader before config selection. The worker preserves consumer cwd, native config cache bookkeeping and absolute selected file arguments; executable user config can explicitly opt into consumer loaders and custom checkers. Engine paths are captured from the tool's installation before that config can change Composer's installed-package metadata. - -The CLI follows the same bootstrap order as Changelog: Composer's injected -``$_composer_autoload_path``, the checkout's ``../vendor/autoload.php``, then -``../../../autoload.php`` relative to the package binary. The last path supports -CPX invoking the installed package binary directly without Composer's proxy. -Resolution never searches the current working directory. Missing dependencies -produce a diagnostic on stderr and exit code 1. - -Ordinary Composer installation inside the consumer intentionally shares that installation's autoloader through its binary proxy. ``composer-runtime-api`` and ``$_composer_autoload_path`` remain part of this contract. Removing an extra import from a different target graph does not remove normal Composer bootstrap. PHPUnit and type-analysis adapters require their own compatibility tests; the formatter's process boundary does not prove their isolation. The separate Rector command's executable configuration and child-autoload limits are documented in the `native refactoring contract `__. - +Tools may bundle libraries at runtime. Libraries may consume tools as development dependencies. A library must not require a development tool for its normal runtime behavior. Tools must not depend on DevTools in production or create a circular tool/bootstrap prerequisite. Code Standards supplies its preset API inside its compiled CLI for explicit configuration; it is not exported as a consumer-autoloaded library. Application tests/reports remain outside its domain. Bundled libraries are development dependencies of the build and do not enter the consumer dependency graph. + +The PHAR separates installation and dependency resolution. It does not isolate PHP namespaces when PHPUnit, Rector or executable configuration loads the consumer and tool in the same process. Version conflicts and global state still require explicit compatibility tests or a real process boundary. A separate Composer graph alone must never be described as runtime isolation. Analyzer/autoload fixtures must prove that the code under analysis comes from the consumer checkout, rather than another copy installed inside the tool. Syntax parsing uses ``php -n -l`` without application autoload. The formatter worker loads its own Composer runtime and uses ECS's public container factory and native check command. It does not invoke ECS's CLI bootstrap, which otherwise imports the working directory's vendor autoloader before config selection. The worker preserves consumer cwd, native config cache bookkeeping and absolute selected file arguments; executable user config can explicitly opt into consumer loaders and custom checkers. Engine paths are captured from the tool's installation before that config can change Composer's installed-package metadata. + +Composer points directly at ``builds/code-standards``. The compiled executable +materializes a private temporary runtime before loading its own source entrypoint, +autoloader, schema and subprocess tools. It preserves the consumer cwd and cleans +its runtime at normal shutdown. The source entrypoint ``bin/code-standards`` remains +for contributor checks and reusable workflow checkouts. The consumer Composer +autoloader is not a bootstrap dependency of the compiled binary. Executable ECS +and Rector configurations may explicitly import consumer classes; see the +`native refactoring contract `__. Bootstrap and compatibility --------------------------- @@ -96,7 +95,7 @@ This increment consumes ``fast-forward/container`` 1.10.1 (release commit ``a75d Use the published ``ContainerFactory::create()`` for composition. Consumers needing explicit bindings may pass one native ``AggregateServiceProvider`` containing the module provider first and their ``ArrayServiceProvider`` last. Later factories replace earlier factories before services are resolved. Keep replacements typed to the declared interface and apply them before first lookup. Tests exercise lazy construction, shared application identity, nested transport/output overrides, native command binding and exact failure status with the published adapter. No module-specific container, provider registry or override DSL exists. -The default provider defines no extensions. This tool does not claim additional cross-provider decoration or alias guarantees beyond the published adapter. CPX installation isolation does not alter these in-process runtime/autoload boundaries. Command configuration does not double as dependency injection: check/fix are separate invocable entrypoints and transport authority is explicit. +The default provider defines no extensions. This tool does not claim additional cross-provider decoration or alias guarantees beyond the published adapter. PHAR installation isolation does not alter these in-process runtime/autoload boundaries. Command configuration does not double as dependency injection: check/fix are separate invocable entrypoints and transport authority is explicit. Consequences and next gates --------------------------- @@ -113,3 +112,10 @@ DevTools #14/#265/#266/#267 remain conditional analysis candidates. Ownership be Doctor #142, distribution #197, published refs #238, orchestration #240/#384 and Actions governance #347–349 remain with DevTools. Current bugs and correction PRs remain there. Historical extensibility/options/config/path contracts are criteria, not automatically reopened tasks. Backlog transfers require a confirmed destination issue with context and links before commenting/closing the DevTools original. Mixed issues retain their DevTools scope. Use the coordinated inventory and existing destinations to avoid duplicates; current DevTools bug PRs stay in that repository. + +PHAR distribution follow-up +--------------------------- + +See the `PHAR distribution investigation `__ for the +single-repository transport, committed executable and CI freshness checks. +The existing Changelog lifecycle publishes the reviewed commit and its compiled artifact. diff --git a/docs/automation.rst b/docs/automation.rst index 843a2e7..9cbfd7b 100644 --- a/docs/automation.rst +++ b/docs/automation.rst @@ -194,7 +194,7 @@ The activation plan preserves branches, without branch-to-tag migration: release is changed as a test. GitHub v0 can span breaking development minors and is independent of Composer -constraints and CPX resolution. No channel is precreated at main. +constraints and Composer resolution. No channel is precreated at main. Checking can fail the workflow. Making it a required status is a separate GitHub protection decision. ``fix`` has no automatic push workflow and never runs during PR checks. Full tracked scans occur for Composer/ECS/EditorConfig/root or nested ``.gitignore`` changes, including removal and rename. Policy-name invalidation is case-insensitive: a case-folding filesystem can let Git read ``.GiTiGnOrE`` as ``.gitignore``. On a case-sensitive filesystem, this conservative detection can cause an extra full scan; it does not change which ignore rules Git applies. After other policy imports change, run an explicit full check in addition to PR checks. diff --git a/docs/composer-normalization.rst b/docs/composer-normalization.rst index 142c81e..c2b992a 100644 --- a/docs/composer-normalization.rst +++ b/docs/composer-normalization.rst @@ -128,11 +128,10 @@ in check mode. Review arbitrary extension metadata in the resulting diff. After CLI bootstrap, normalization uses only data APIs. It never constructs a Composer application, plugin manager or installer, starts Composer, discovers an additional target-project autoload/config, executes Composer script commands or -fetches a remote schema. In an ordinary consumer Composer installation, the -binary proxy intentionally loads the shared consumer/tool autoloader: registered -autoload files can execute during that existing bootstrap. A separate tool -installation does not import the target project's vendor/autoload.php; the -poison-autoload fixture verifies that separate-installation case. See the +fetches a remote schema. The compiled binary loads its extracted tool autoloader +and schema, including through Composer's binary proxy. It does not import the +consumer autoloader; the PHAR qualification includes a poisoned consumer loader. +Executable ECS/Rector configuration remains a separate opt-in boundary. See the `architecture contract `__. This is not a filesystem sandbox and does not support concurrent edits to the same metadata. @@ -156,10 +155,9 @@ The smaller alternative is the standalone normalizer plus ``composer/semver`` and a copied official schema. It would still need a separate lock-hash implementation or weaker lock validation: neither is provided by the reviewed standalone normalizer API. The Composer plugin would require consumer plugin -policy changes; invoking it manually would defeat that boundary. A PHAR could -isolate its package graph but would introduce artifact verification, packaging -and a second runtime/distribution lifecycle. This increment selects the native -library/hash APIs rather than those additional contracts. +policy changes; invoking it manually would defeat that boundary. The PHAR bundles +these native library/hash APIs and schema; the consumer does not resolve this +internal graph. See the `distribution contract `__. Library autoloading is still executable code. The newly added graph includes React Promise's conditional function include and Symfony's PHP polyfill @@ -167,13 +165,13 @@ bootstraps. These were inspected in the resolved releases; they define helper functions/constants, with version-gated polyfill setup, and do not install or activate Composer plugins. No newly added package has ``composer-plugin`` type. These files load with the tool autoloader even when normalization is not chosen; -this integration does not claim namespace or runtime isolation. Composer must -resolve compatibility with a normal consumer installation. +this integration does not claim a security or namespace sandbox. These packages +belong to the build graph, independently of the consumer Composer solver. The direct constraints use caret ranges (``^4.11.0`` normalizer, ``^2.10.3`` Composer, ``^1.7.0`` JSON and ``^3.9.1`` printer), allowing compatible minor/patch updates up to the next major. The lock records the exact verified -versions; consumers resolve their own graph within those ranges. The minimum +versions used to build the ready artifact. Contributors resolve these ranges. The minimum versions and current locked graph are tested; this does not claim execution of unreleased versions admitted by those ranges. Schema content/order therefore belongs to the installed Composer package. diff --git a/docs/faq.rst b/docs/faq.rst index 14eeba9..8851034 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -44,7 +44,7 @@ Can I assume v0 and 0.1.0 exist? No. Verify actual releases and channel refs independently before adopting Composer constraints or copying a workflow wrapper. -Does CPX isolate all library namespaces? +Does the compiled executable isolate all library namespaces? No. Separate installation does not isolate classes loaded in one PHP process. How is the application composed? diff --git a/docs/getting-started/installation.rst b/docs/getting-started/installation.rst index 9eadbd3..f3d8e89 100644 --- a/docs/getting-started/installation.rst +++ b/docs/getting-started/installation.rst @@ -1,29 +1,40 @@ Installation and compatibility ============================== -The tool requires PHP 8.3 or later within the supported 8.x range, Git, and -Composer runtime API 2.2 or later. Contributor builds additionally need Bash, jq and Node.js 24 or later for -workflow contract tests. The release-channel selection fixtures run through -``node --test`` as part of ``composer test``. The locked contributor graph is tested on -PHP 8.3, 8.4 and 8.5. +Install a verified published Composer version in the consumer Git project: -For a source checkout of a reviewed commit: +.. code-block:: console + + composer require --dev fast-forward/code-standards + vendor/bin/code-standards list + vendor/bin/code-standards check --all + +Composer's binary proxy targets ``builds/code-standards`` directly. The package +contains a ready executable; consumers run no build and resolve no internal tool +libraries. PHP ^8.3, Git, and the extensions declared in ``composer.json`` are +required, including Phar and zlib. Native XML magic-number reports also need DOM. +The executing PHP parses selected sources; it does not emulate an older PHP. + +Optional Composer scripts may invoke ``code-standards check --all`` and +``code-standards fix --all``. The latter requires explicit formatting authority. +Global use installs with ``composer global require fast-forward/code-standards``; +find its executable directory with ``composer global config bin-dir --absolute``. + +Use an actual published version when adopting this distribution contract. +A source merge, draft PR or GitHub workflow channel does not prove a ready +Packagist distribution exists. See `PHAR distribution <../phar-distribution.rst>`__. +``--prefer-source`` includes source files alongside the committed executable; +both Composer routes execute the ready binary without a consumer build. + +Contributors retain the separate source entrypoint: .. code-block:: console composer install --no-plugins --no-scripts php bin/code-standards help + composer build -Use an actual published Composer version when adopting the package as a -``require-dev`` dependency. Check the repository releases first: documentation -of current source does not establish that a package version or the GitHub -``v0`` channel has been published. A local path repository is suitable for an -isolated trial; do not mistake its development constraint for a release. - -The package's runtime dependencies are libraries, including Symfony Console, -Symfony Process, Fast Forward Container and ECS. The tool's ``require-dev`` -dependencies are not installed transitively for consumers. CPX separates -installation and resolution, not PHP namespaces loaded into one process. - -The executing PHP binary parses selected sources; it is not an emulator for a -consumer's older PHP minimum. No PHP version migration is enabled. +Build dependencies live in ``require-dev``. Builds resolve locked runtime roots +inside a disposable directory; they never consume Code Standards itself. Workflow +tests additionally need Bash, jq and Node.js 24 or later. The contributor graph +and compiled executable are tested on PHP 8.3, 8.4 and 8.5. diff --git a/docs/index.rst b/docs/index.rst index 69d1e22..c70b037 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -25,6 +25,7 @@ Architecture, policy and evidence :maxdepth: 1 architecture + phar-distribution automation composer-normalization instruction-map diff --git a/docs/links/dependencies.rst b/docs/links/dependencies.rst index 3d31dc6..9c5881d 100644 --- a/docs/links/dependencies.rst +++ b/docs/links/dependencies.rst @@ -27,6 +27,7 @@ The explicit semantic command uses Rector with PHPStan as an engine dependency; the `native refactoring guide <../rector.rst>`__ records the qualified dependency versions and shared-graph compatibility restriction. No type-analysis command is implied by that dependency. -Composer's runtime API supplies the installed binary proxy's autoload location. +Libraries are bundled inside the PHAR. The consumer dependency graph contains +only the tool package and declared PHP/platform requirements. The lock file records transitive versions; this documentation does not replace that graph with a handwritten dependency catalog. diff --git a/docs/optional-analysis.rst b/docs/optional-analysis.rst index d09493d..a677457 100644 --- a/docs/optional-analysis.rst +++ b/docs/optional-analysis.rst @@ -33,7 +33,7 @@ The consumer wrapper in ``resources/workflows/code-standards.yml`` provides the basic PR style check through the published ``v0`` channel. The fragment below describes the optional-analysis inputs. The selected workflow revision defines its supported input contract; callers supply inputs belonging to that revision. -GitHub workflow channels and Composer/CPX package constraints are separate +GitHub workflow channels and Composer package constraints are separate distribution choices. The unprivileged PR event and runtime/ref provenance contract apply to both basic checks and explicitly enabled analyses. diff --git a/docs/phar-distribution.rst b/docs/phar-distribution.rst new file mode 100644 index 0000000..127d4af --- /dev/null +++ b/docs/phar-distribution.rst @@ -0,0 +1,156 @@ +Compiled distribution +===================== + +The source is maintained once in this repository. ``composer build`` invokes the +PHP builder at ``scripts/phar/build.php`` and writes the executable +``builds/code-standards``. Composer's ``bin`` points directly at that file, following +the concrete ``builds/cpx`` layout in +`CPX 2.1.0 `__. +The separate ``bin/code-standards`` remains the contributor source entrypoint. +There is no CPX dependency, installer, updater or consumer build hook. + +Build +----- + +.. code-block:: console + + composer install --no-plugins --no-scripts + composer build + php builds/code-standards list --raw + php scripts/phar/verify.php builds/code-standards + +The builder uses an exclusive temporary tree, promotes the explicit runtime +roots listed in ``extra.phar.runtime-packages`` to their exact locked versions, +and asks Composer to resolve that graph using only locked package metadata. +It invokes the Composer executable from the installed locked ``composer/composer`` +package, so an unrelated global Composer update cannot change generated lock metadata. +Contributor/test packages are excluded by resolution, rather than stripping +files from dependencies. Generated proxies, resources and licenses are retained. +The source manifests, lock and installed vendor are never rewritten by the build. +The payload's root version comes from the tracked Composer manifest, defaulting +to ``dev-main`` when absent; ambient Composer root-version guesses do not change +its executable contents. +This is the same necessary dependency promotion as +`CPX's builder `__; +simply moving requires to require-dev would omit the runtime from a no-dev build. + +The GZ PHAR has a SHA-256 integrity signature, an embedded version/reference +inventory and executable mode. Compression requires ext-zlib. Phar and all +extensions required by the locked runtime are declared in Composer metadata; +PHP-only is not an accurate portability claim. DOM remains optional for XML. +The earlier uncompressed proof was 142,765,641 bytes; GZ was 48,600,589 bytes. +The final size varies with source/locked packages. Libraries occur inside the +artifact, and outer copyable resources also occur inside it; source maintenance +is single, bytes are not deduplicated. + +Filesystem runtime +------------------ + +The current engines use realpath, workers, filesystem entrypoints and Composer's +schema. PHP's phar stream cannot satisfy these unchanged filesystem contracts. +The executable therefore extracts into a random private 0700 temporary directory +and deletes it at normal shutdown. There is no shared cache. Abrupt termination +can leave that directory behind. This is process separation, not a namespace +sandbox: executable consumer configurations may load consumer classes explicitly. +CWD, project/config paths and the check command's source-preservation contract +continue to apply. + +The default ECS cache lives inside that extracted runtime and is removed with it. +Native cache metadata contains installation paths, so independently extracted +invocations deliberately do not reuse it. Repeated compiled checks/fixes do not +accumulate persistent per-invocation policy directories in system temporary storage. +Source installations retain their stable checkout/policy cache identity; explicit +native ``withCache()`` overrides remain authoritative and outside this cleanup. + +Consumer ECS configuration +-------------------------- + +The tool's default preset does not require the consumer's Composer loader or +discover ``ecs.php`` automatically. Select the project configuration explicitly +with ``vendor/bin/code-standards check --all --config=ecs.php``. To use project +classes or development rule packages, that configuration opts into its own loader: + +.. code-block:: php + + withRules([ + Project\CodingStandard\ProjectFixer::class, + Vendor\CodingStandard\PackageFixer::class, + ]); + +Install those rule packages in the project's ``require-dev``. Composer generates +project ``autoload`` and ``autoload-dev`` mappings during development installation; +``composer install --no-dev`` intentionally omits development packages/mappings. +Rules must implement APIs compatible with the bundled ECS/PHP-CS-Fixer/PHPCS +versions. Loading the project autoloader is explicit execution inside the worker, +not namespace isolation or a guarantee that conflicting engine versions coexist. + +The distribution harness installs a local synthetic rule package through real +Composer ``require-dev`` and registers separate project ``autoload`` and +``autoload-dev`` fixers. It verifies all three rules report their own marker, +``check`` preserves source, ``fix`` changes every marker, and rechecking succeeds +without changing Composer metadata or configuration. Existing source integration +tests also exercise explicit loader opt-in; their synthetic loader alone was not +proof of Composer-generated development autoloading in the PHAR. + +Standard Composer distribution +------------------------------- + +``builds/code-standards`` is committed in this same repository. Packagist's +standard GitHub zipball therefore contains the ready executable from the tagged +tree. ``composer.json`` points directly at it. Neither a consumer build nor +custom dist metadata nor discovery of release assets is necessary. + +The Repository Quality workflow builds and verifies a fresh executable on PHP +8.3, 8.4 and 8.5. Its PHP 8.3 job uploads that build as an Actions artifact named +after the exact commit SHA before checking the committed artifact. A stale +committed binary therefore fails CI while the fresh reviewed build remains +available to update that PR. +Before executing the committed candidate, CI opens it only as archive data and +compares the entire stub and sorted path/content hashes with that fresh build. +Extra/missing/changed entries, links and serialized metadata are rejected. +Self-reported manifest digests are diagnostic bindings, not a trust anchor for +the executable. Container timestamps/compression are not executable content. +It uses contents-read permissions, does not write Git or tags, and runs no +release. Maintainers can obtain the artifact from that reviewed run, restore +executable mode with ``chmod +x builds/code-standards``, and include it in the +same PR as source/manifest/lock/build-recipe changes. Alternatively ``composer build`` +replaces the tracked executable locally. CI rejects a missing or stale binary +by comparing actual runtime identity, source/resource hashes, the original lock +digest, Composer build manifest and PHP build recipe with the reviewed checkout. + +This puts the binary in the reviewed commit before Changelog's existing +version-PR/tag lifecycle operates. Changelog remains the only release/tag owner; +no release workflow or token permissions are changed here. A future release's +tag retains the artifact already in its commit. A merge still does not itself +prove a release has been published or indexed by Packagist. + +``.gitattributes`` exports the compiled file, Composer manifests, license and +entire ``resources/`` tree, including skills and future templates. Source, +config, tests and contributor scripts are excluded from archives. Copyable +workflow callers remain under ``resources/workflows``; executable reusable +workflows remain under ``.github/workflows`` for existing ``uses`` calls. +``--prefer-source`` includes source files alongside the ready committed binary; +it ignores export-ignore but still needs no consumer build. Contributors use +the separate source entrypoint and dev dependencies when changing the tool. + +The binary is about 46.4 MiB with the current lock. This intentionally versions +compiled library bytes in Git to support the default Packagist distribution. +Source maintenance remains single, while binary bytes and outer resources are +not deduplicated. A GitHub release asset alone would not automatically replace +the tagged-tree zipball; no alternate transport is configured. + +Qualification +------------- + +The PHP harness creates disposable Git/Composer ZIPs, compares every exported +file hash, preserves the complete resource tree, and installs the actual ready +ZIP through isolated Composer local/global directories. It checks the generated +binary proxy points directly at the compiled file, a single consumer solver +package, no exported source, no-dev reinstall, native formatter/Rector/MND output +and source preservation, and bootstrapping despite a poisoned consumer loader. +The repository CI runs this build and harness on PHP 8.3, 8.4 and 8.5. Those tests +qualify a synthetic distribution; publication remains owned by the existing reviewed Changelog lifecycle. diff --git a/docs/rector.rst b/docs/rector.rst index aa1c55f..072e4db 100644 --- a/docs/rector.rst +++ b/docs/rector.rst @@ -3,7 +3,7 @@ Explicit semantic refactoring The opt-in ``refactor`` command previews native semantic transformations and applies them only when explicitly requested. It does not select a migration profile or authorize publication. ``check`` and ``fix`` never invoke the semantic runner. The separate invocable command uses the existing container factories and process transport. -The manifest requires Rector ``^2.8.2`` and PHPStan ``^2.3.3``. Rector 2.4.2 accessed PHPStan's internal ``RichParser::$container`` property, which is absent in newer PHPStan versions; that older combination failed before rule execution. Rector 2.8.2 creates its parser through a native factory compatible with PHPStan 2.3.3. The minimum versions advance together so consumers cannot resolve the older incompatible Rector with newer PHPStan. Both constraints permit subsequent compatible minor releases. PHPStan is an engine dependency here, not a newly exposed analysis command. Dependency updates require the real integration fixtures below. Native configuration is the only extension point; no PHP parser, configuration translator or engine framework is introduced. Configuration paths resolve from ``--directory`` or the current directory. This command deliberately uses the configuration's whole-project paths rather than borrowing the formatter's changed-file list. +The build manifest requires Rector ``^2.8.2`` and PHPStan ``^2.3.3``. Rector 2.4.2 accessed PHPStan's internal ``RichParser::$container`` property, which is absent in newer PHPStan versions; that older combination failed before rule execution. Rector 2.8.2 creates its parser through a native factory compatible with PHPStan 2.3.3. The minimum versions advance together so contributor builds cannot resolve the older incompatible Rector with newer PHPStan. Consumers receive that locked pair inside the ready executable. Both constraints permit subsequent compatible minor releases. PHPStan is an engine dependency here, not a newly exposed analysis command. Dependency updates require the real integration fixtures below. Native configuration is the only extension point; no PHP parser, configuration translator or engine framework is introduced. Configuration paths resolve from ``--directory`` or the current directory. This command deliberately uses the configuration's whole-project paths rather than borrowing the formatter's changed-file list. .. code-block:: php diff --git a/docs/resolved-preset.rst b/docs/resolved-preset.rst index 1dd70a7..68d7718 100644 --- a/docs/resolved-preset.rst +++ b/docs/resolved-preset.rst @@ -213,7 +213,10 @@ The native ECS spacing service supplies two/four-space or tab indentation and LF/CRLF endings after the consumer callback. Native ``withSkip()`` can disable the owned checker explicitly; it replaces the skip list, so callers preserve their other intentional skips. The default cache fingerprints all three installed policy -source paths and bytes (preset plus both Sniffs), in addition to checkout isolation. An explicit +source paths and bytes (preset plus both Sniffs), in addition to checkout isolation. +Source installations reuse that identity. A compiled distribution keeps its +default cache inside its exclusive extracted runtime, whose shutdown cleanup +removes the cache; random extraction paths cannot leak persistent policy directories. An explicit ``withCache()`` override remains authoritative. CLI regressions cover the reported mixed-line guard, groups/calls/indexes, diff --git a/resources/skills/fast-forward-code-standards/SKILL.md b/resources/skills/fast-forward-code-standards/SKILL.md index 09396dd..1f4944d 100644 --- a/resources/skills/fast-forward-code-standards/SKILL.md +++ b/resources/skills/fast-forward-code-standards/SKILL.md @@ -2,7 +2,7 @@ name: fast-forward-code-standards description: Use Fast Forward Code Standards to check PHP syntax and formatting, apply an explicitly requested style fix, configure native ECS overrides, preview opt-in Rector changes, analyze magic numbers, or adopt its changed-file PR workflow. Use fast-forward-phpdoc for source-documentation authoring instead. license: MIT -compatibility: Supports fast-forward/code-standards ^0.5.0, PHP ^8.3, Git and Composer runtime API ^2.2. Native XML magic-number reports additionally require ext-dom. Inspect the installed version and help; do not silently upgrade an incompatible consumer. +compatibility: Supports fast-forward/code-standards ^0.5.0, PHP ^8.3, Git and the PHP extensions declared by the installed package (including Phar and zlib). Native XML magic-number reports additionally require ext-dom. Inspect the installed version and help; do not silently upgrade an incompatible consumer. metadata: api-baseline: fast-forward/code-standards ^0.5.0 repository: https://github.com/php-fast-forward/code-standards @@ -29,6 +29,13 @@ vendor/bin/code-standards help check vendor/bin/code-standards check --all ``` +Composer installs the compiled `builds/code-standards` executable and creates +`vendor/bin/code-standards`; consumers do not build it or install its internal +libraries. Adopt only a published version whose distribution contains that artifact. +A source merge alone is insufficient. Optional Composer scripts can call +`code-standards check --all` and `code-standards fix --all`; only invoke the latter +when a fix is explicitly requested. Global use follows `composer global require +fast-forward/code-standards` and Composer's global bin directory. No config file is required for the packaged PER Coding Style 3.0 preset. `--all` selects tracked PHP files and PHP entrypoints under `bin/`, excluding matches of the consumer's root/nested `.gitignore`, including force-added tracked matches. diff --git a/resources/skills/fast-forward-code-standards/references/configuration-and-ci.md b/resources/skills/fast-forward-code-standards/references/configuration-and-ci.md index 8c96a0a..48610c5 100644 --- a/resources/skills/fast-forward-code-standards/references/configuration-and-ci.md +++ b/resources/skills/fast-forward-code-standards/references/configuration-and-ci.md @@ -76,7 +76,7 @@ jobs: Use `v0` only when it points to a confirmed published 0.x release; a reviewed immutable workflow commit is another explicit policy. This GitHub channel is -not a Composer/CPX constraint. Runtime/helper source stays bound to the actual +not a Composer constraint. Runtime/helper source stays bound to the actual called workflow repository/SHA, never a second moving-ref lookup. Use only inputs supported by that ref. diff --git a/resources/workflows/README.md b/resources/workflows/README.md new file mode 100644 index 0000000..bb11f51 --- /dev/null +++ b/resources/workflows/README.md @@ -0,0 +1,15 @@ +# Copyable workflow callers + +`code-standards.yml` is the canonical consumer caller template. Copy it to the +consumer's `.github/workflows/code-standards.yml`, then review the published +reference and inputs. Future DevTools orchestration may copy this resource from +the installed package. The complete `resources/` tree is part of distribution. + +The executable reusable remains at `.github/workflows/code-standards.yml` in +this repository, with its supporting check and analysis workflows. GitHub +resolves reusable `uses:` paths there; a file in `resources/` cannot replace +that endpoint. The template calls that endpoint rather than duplicating its +implementation. This matches Changelog's `resources/workflows/changelog.yml` +caller and `.github/workflows/changelog.yml` executable contract. + +No installer or workflow synchronization runs on the consumer automatically. diff --git a/resources/workflows/code-standards.yml b/resources/workflows/code-standards.yml index eee55f9..7e573e9 100644 --- a/resources/workflows/code-standards.yml +++ b/resources/workflows/code-standards.yml @@ -1,3 +1,5 @@ +# Copy this caller into the consumer's .github/workflows/code-standards.yml. +# The executable reusable remains in the called repository's .github/workflows/. name: PHP standards on: pull_request: diff --git a/scripts/phar/build.php b/scripts/phar/build.php new file mode 100644 index 0000000..a4f36dc --- /dev/null +++ b/scripts/phar/build.php @@ -0,0 +1,81 @@ + 2 || ($argc > 1 && file_exists($output))) { + throw new RuntimeException('Usage: php -d phar.readonly=0 scripts/phar/build.php [NEW_OUTPUT]'); +} +$recipe = buildHashes($root); +$stub = file_get_contents(__DIR__ . '/stub.php'); +$recipe['scripts/phar/stub.php'] = hash('sha256', $stub); +$runtime = sys_get_temp_dir() . '/standards-build-' . bin2hex(random_bytes(12)); +mkdir($runtime, 0700); +register_shutdown_function(static fn() => removeTree($runtime)); +foreach (['src', 'config', 'resources', 'bin', 'LICENSE'] as $path) { + copyTree($root . '/' . $path, $runtime . '/' . $path); +} +$sourceContent = file_get_contents($root . '/composer.json'); +$source = json_decode($sourceContent, true, flags: JSON_THROW_ON_ERROR); +$lockContent = file_get_contents($root . '/composer.lock'); +$lock = json_decode($lockContent, true, flags: JSON_THROW_ON_ERROR); +$packages = array_merge($lock['packages'], $lock['packages-dev']); +$versions = array_column($packages, 'version', 'name'); +$require = $source['require']; +foreach ($source['extra']['phar']['runtime-packages'] as $name) { + $require[$name] = $versions[$name] ?? throw new RuntimeException('Missing locked runtime root: ' . $name); +} +$manifest = [ + 'name' => $source['name'], + 'license' => $source['license'], + 'version' => $source['version'] ?? 'dev-main', + 'require' => $require, + 'autoload' => ['psr-4' => ['FastForward\\CodeStandards\\' => 'src/']], + 'repositories' => [['type' => 'package', 'package' => $packages], ['packagist.org' => false]], + 'config' => ['platform' => $source['config']['platform'], 'allow-plugins' => false], +]; +writeJson($runtime . '/composer.json', $manifest); +runCommand([PHP_BINARY, $root . '/vendor/composer/composer/bin/composer', 'update', '--no-dev', '--no-plugins', '--no-scripts', '--no-interaction', '--prefer-dist', '--no-progress', '--no-audit'], $runtime); +$installed = json_decode(file_get_contents($runtime . '/vendor/composer/installed.json'), true, flags: JSON_THROW_ON_ERROR); +if (false !== $installed['dev']) { + throw new RuntimeException('Build unexpectedly contains development packages.'); +} +$identity = require $runtime . '/vendor/composer/installed.php'; +$inventory = []; +foreach ($installed['packages'] as $package) { + $inventory[$package['name']] = ['version' => $package['version'], 'reference' => $package['source']['reference'] ?? $package['dist']['reference'] ?? null]; +} +ksort($inventory); +writeJson($runtime . '/distribution-manifest.json', [ + 'root' => array_intersect_key($identity['root'], array_flip(['name', 'version', 'pretty_version', 'reference'])), + 'composer_lock_sha256' => hash('sha256', $lockContent), + 'composer_json_sha256' => hash('sha256', $sourceContent), + 'source_files_sha256' => inputHashes($runtime), + 'build_recipe_sha256' => $recipe, + 'packages' => $inventory, + 'compression' => 'GZ', +]); +if (!is_dir(dirname($output))) { + mkdir(dirname($output), 0755, true); +} +$temporary = $output . '.' . bin2hex(random_bytes(8)) . '.phar'; +register_shutdown_function(static function () use ($temporary): void { + if (is_file($temporary)) { + unlink($temporary); + } +}); +$archive = new Phar($temporary); +$archive->startBuffering(); +$archive->buildFromDirectory($runtime); +$archive->setStub($stub); +$archive->setSignatureAlgorithm(Phar::SHA256); +$archive->compressFiles(Phar::GZ); +$archive->stopBuffering(); +unset($archive); +chmod($temporary, 0755); +rename($temporary, $output); +fwrite(STDOUT, hash_file('sha256', $output) . ' ' . $output . PHP_EOL); diff --git a/scripts/phar/compare.php b/scripts/phar/compare.php new file mode 100644 index 0000000..c160625 --- /dev/null +++ b/scripts/phar/compare.php @@ -0,0 +1,48 @@ + removeTree($directory)); +// Open executable bytes as data. Never include, map or execute the committed candidate. +copy($argv[1], $directory . '/fresh.phar'); +copy($argv[2], $directory . '/committed.phar'); +$fresh = new Phar($directory . '/fresh.phar'); +$committed = new Phar($directory . '/committed.phar'); +if ($fresh->getStub() !== $committed->getStub()) { + throw new RuntimeException('Committed PHAR stub differs from the fresh build.'); +} +$contents = static function (Phar $archive): array { + if (!$archive->isFileFormat(Phar::PHAR) || $archive->hasMetadata()) { + throw new RuntimeException('Expected a native PHAR without serialized metadata.'); + } + $prefix = 'phar://' . $archive->getPath() . '/'; + $entries = []; + foreach (new RecursiveIteratorIterator($archive, RecursiveIteratorIterator::SELF_FIRST) as $entry) { + if ($entry->isLink() || $entry->hasMetadata()) { + throw new RuntimeException('PHAR entries must not contain links or serialized metadata.'); + } + $name = substr($entry->getPathname(), strlen($prefix)); + $hash = $entry->isDir() ? 'directory' : hash_file('sha256', $entry->getPathname()); + if (false === $hash) { + throw new RuntimeException('Cannot read a PHAR entry.'); + } + $entries[$name] = $hash; + } + ksort($entries); + + return $entries; +}; +$expected = $contents($fresh); +$actual = $contents($committed); +if ($expected !== $actual) { + $different = array_keys(array_diff_assoc($expected, $actual) + array_diff_assoc($actual, $expected)); + throw new RuntimeException('Committed PHAR contents differ from the fresh build: ' . json_encode(array_slice($different, 0, 10), JSON_THROW_ON_ERROR)); +} +fwrite(STDOUT, "Committed PHAR stub and complete contents match the fresh build.\n"); diff --git a/scripts/phar/consumer-config.php b/scripts/phar/consumer-config.php new file mode 100644 index 0000000..7e18bcd --- /dev/null +++ b/scripts/phar/consumer-config.php @@ -0,0 +1,89 @@ + removeTree($directory)); + $template = file_get_contents(dirname(__DIR__, 2) . '/tests/Fixtures/BootstrapFixer.php.inc'); + $rules = [ + 'FixtureRuntime' => 'runtime-rules', + 'FixtureDevelopment' => 'development-rules', + 'FixturePackage' => 'package/src', + ]; + foreach ($rules as $namespace => $path) { + mkdir($directory . '/' . $path, 0755, true); + file_put_contents($directory . '/' . $path . '/BootstrapFixer.php', str_replace( + ['CodeStandardsFixture', 'Fixture/replace_marker', 'needs-fix', "'fixed'"], + [$namespace, $namespace . '/replace_marker', $namespace . '-before', "'" . $namespace . "-after'"], + $template, + )); + } + writeJson($directory . '/package/composer.json', [ + 'name' => 'fixture/development-rules', + 'version' => '1.0.0', + 'autoload' => ['psr-4' => ['FixturePackage\\' => 'src/']], + ]); + writeJson($directory . '/composer.json', [ + 'name' => 'fixture/consumer-rules', + 'repositories' => [['type' => 'path', 'url' => 'package', 'options' => ['symlink' => false]], ['packagist.org' => false]], + 'require-dev' => ['fixture/development-rules' => '1.0.0'], + 'autoload' => ['psr-4' => ['FixtureRuntime\\' => 'runtime-rules/']], + 'autoload-dev' => ['psr-4' => ['FixtureDevelopment\\' => 'development-rules/']], + ]); + runCommand(['composer', 'update', '--no-plugins', '--no-scripts', '--no-interaction', '--no-progress', '--no-audit'], $directory, environment: $environment); + $installed = json_decode(file_get_contents($directory . '/vendor/composer/installed.json'), true, flags: JSON_THROW_ON_ERROR); + if (!in_array('fixture/development-rules', $installed['dev-package-names'], true)) { + throw new RuntimeException('Fixture rule package was not installed as a development dependency.'); + } + $source = "withRules([ + FixtureRuntime\BootstrapFixer::class, + FixtureDevelopment\BootstrapFixer::class, + FixturePackage\BootstrapFixer::class, +]); +CONFIG); + runCommand(['git', 'init', '-q'], $directory, environment: $environment); + runCommand(['git', 'add', 'source.php'], $directory, environment: $environment); + $metadata = ['composer.json', 'composer.lock', 'vendor/autoload.php', 'ecs.php']; + $before = array_map(static fn(string $path) => hash_file('sha256', $directory . '/' . $path), $metadata); + runCommand([PHP_BINARY, $executable, 'check', '--all'], $directory, environment: $environment); + if (file_exists($directory . '/config-executed')) { + throw new RuntimeException('Consumer ECS configuration was implicitly discovered.'); + } + $output = runCommand([PHP_BINARY, $executable, 'check', '--all', '--config=ecs.php'], $directory, 2, $environment); + if ($source !== file_get_contents($directory . '/source.php') || !is_file($directory . '/config-executed')) { + throw new RuntimeException('Explicit consumer configuration did not run read-only.'); + } + foreach (array_keys($rules) as $namespace) { + if (!str_contains($output, $namespace . '-after')) { + throw new RuntimeException('Consumer rule did not detect its fixture: ' . $namespace); + } + } + runCommand([PHP_BINARY, $executable, 'fix', '--all', '--config=ecs.php'], $directory, environment: $environment); + $fixed = file_get_contents($directory . '/source.php'); + foreach (array_keys($rules) as $namespace) { + if (!str_contains($fixed, "'" . $namespace . "-after'") || str_contains($fixed, $namespace . '-before')) { + throw new RuntimeException('Consumer rule did not fix its fixture: ' . $namespace); + } + } + runCommand([PHP_BINARY, $executable, 'check', '--all', '--config=ecs.php'], $directory, environment: $environment); + if ($fixed !== file_get_contents($directory . '/source.php') || $before !== array_map(static fn(string $path) => hash_file('sha256', $directory . '/' . $path), $metadata)) { + throw new RuntimeException('Consumer verification changed source or Composer/configuration inputs.'); + } + + return ['autoload' => true, 'autoload_dev' => true, 'require_dev_rule' => true, 'check_exit' => 2, 'fix_exit' => 0, 'recheck_exit' => 0]; +} diff --git a/scripts/phar/stub.php b/scripts/phar/stub.php new file mode 100644 index 0000000..0b08b15 --- /dev/null +++ b/scripts/phar/stub.php @@ -0,0 +1,31 @@ +#!/usr/bin/env php +isDir() && !$file->isLink()) { + rmdir($file->getPathname()); + } else { + unlink($file->getPathname()); + } + } + rmdir($runtime); +}); +(new Phar(__FILE__))->extractTo($runtime); +$_composer_autoload_path = $runtime . '/vendor/autoload.php'; +require $runtime . '/bin/code-standards'; +__HALT_COMPILER(); diff --git a/scripts/phar/support.php b/scripts/phar/support.php new file mode 100644 index 0000000..e4acec0 --- /dev/null +++ b/scripts/phar/support.php @@ -0,0 +1,114 @@ +isDot()) { + copyTree($file->getPathname(), $target . '/' . $file->getFilename()); + } + } +} + +/** Removes an exclusively owned tree without following links. */ +function removeTree(string $path): void +{ + if (!file_exists($path) && !is_link($path)) { + return; + } + if (is_dir($path) && !is_link($path)) { + foreach (new DirectoryIterator($path) as $file) { + if (!$file->isDot()) { + removeTree($file->getPathname()); + } + } + rmdir($path); + + return; + } + unlink($path); +} + +/** @param array $value */ +function writeJson(string $path, array $value): void +{ + file_put_contents($path, json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . PHP_EOL); +} + +/** @return array Maintained inputs bound to the compiled payload. */ +function inputHashes(string $root): array +{ + $hashes = ['LICENSE' => hash_file('sha256', $root . '/LICENSE')]; + foreach (['src', 'config', 'bin', 'resources'] as $path) { + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root . '/' . $path, FilesystemIterator::SKIP_DOTS)) as $file) { + if ($file->isFile()) { + $hashes[substr($file->getPathname(), strlen($root) + 1)] = hash_file('sha256', $file->getPathname()); + } + } + } + ksort($hashes); + + return $hashes; +} + +/** @return array The PHP recipe that produced the executable. */ +function buildHashes(string $root): array +{ + $hashes = []; + foreach (['build.php', 'stub.php', 'support.php'] as $file) { + $path = 'scripts/phar/' . $file; + $hashes[$path] = hash_file('sha256', $root . '/' . $path); + } + + return $hashes; +} + +/** + * @param list $arguments + * @param array $environment Explicit isolated overrides. + */ +function runCommand(array $arguments, string $directory, int $expected = 0, array $environment = []): string +{ + static $home = null; + if (null === $home) { + $home = sys_get_temp_dir() . '/standards-composer-' . bin2hex(random_bytes(12)); + mkdir($home, 0700); + register_shutdown_function(static fn() => removeTree($home)); + } + $isolated = []; + foreach (array_keys(getenv()) as $name) { + if (str_starts_with($name, 'COMPOSER') || str_starts_with($name, 'GIT_')) { + $isolated[$name] = false; + } + } + $isolated = array_replace($isolated, [ + 'COMPOSER_HOME' => $home, + 'COMPOSER_CACHE_DIR' => $home . '/cache', + 'GIT_CONFIG_GLOBAL' => '/dev/null', + 'GIT_CONFIG_SYSTEM' => '/dev/null', + 'GIT_TERMINAL_PROMPT' => '0', + ], $environment); + $process = new Symfony\Component\Process\Process($arguments, $directory, $isolated, timeout: 300); + $process->run(); + if ($expected !== $process->getExitCode()) { + throw new RuntimeException('Command failed: ' . implode(' ', $arguments) . PHP_EOL . $process->getOutput() . $process->getErrorOutput()); + } + + return $process->getOutput(); +} diff --git a/scripts/phar/verify.php b/scripts/phar/verify.php new file mode 100644 index 0000000..8f9e9fe --- /dev/null +++ b/scripts/phar/verify.php @@ -0,0 +1,162 @@ + removeTree($root)); +$environment = ['COMPOSER_HOME' => $root . '/composer-home', 'COMPOSER_CACHE_DIR' => $root . '/composer-cache', + 'GIT_CONFIG_GLOBAL' => '/dev/null', 'GIT_CONFIG_SYSTEM' => '/dev/null', 'GIT_TERMINAL_PROMPT' => '0', 'TMPDIR' => $root . '/temporary']; +$package = $root . '/package'; +mkdir($package); +foreach (['src', 'config', 'bin', 'docs', 'composer.json', 'composer.lock', 'LICENSE', 'README.md', 'resources', '.gitattributes'] as $path) { + copyTree($source . '/' . $path, $package . '/' . $path); +} +copyTree($binary, $package . '/builds/code-standards'); +chmod($package . '/builds/code-standards', 0755); +runCommand(['git', 'init', '-q', '-b', 'main'], $package, environment: $environment); +runCommand(['git', 'add', '-f', '.'], $package, environment: $environment); +runCommand(['git', '-c', 'user.name=Package Fixture', '-c', 'user.email=fixture@example.invalid', '-c', 'commit.gpgsign=false', '-c', 'core.hooksPath=/dev/null', 'commit', '-qm', 'Disposable compiled fixture'], $package, environment: $environment); +$reference = trim(runCommand(['git', 'rev-parse', 'HEAD'], $package, environment: $environment)); +runCommand(['git', 'archive', '--format=zip', '--output=' . $root . '/git.zip', 'HEAD'], $package, environment: $environment); +runCommand(['composer', 'archive', '--format=zip', '--dir=' . $root, '--file=composer', '--no-plugins', '--no-scripts'], $package, environment: $environment); +$contents = static function (string $file): array { + $archive = new PharData($file); + $prefix = 'phar://' . $archive->getPath() . '/'; + $files = []; + foreach (new RecursiveIteratorIterator($archive) as $entry) { + if ($entry->isFile()) { + $files[substr($entry->getPathname(), strlen($prefix))] = hash('sha256', $entry->getContent()); + } + } + ksort($files); + + return $files; +}; +$git = $contents($root . '/git.zip'); +if ($git !== $contents($root . '/composer.zip') || !isset($git['builds/code-standards'])) { + throw new RuntimeException('Archive routes disagree or omit the compiled executable.'); +} +foreach (array_keys($git) as $path) { + if (preg_match('~^(?:src|config|bin|tests|scripts)/~', $path)) { + throw new RuntimeException('Source/development input leaked into the ready archive: ' . $path); + } +} +foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source . '/resources', FilesystemIterator::SKIP_DOTS)) as $file) { + if ($file->isFile() && ($git[substr($file->getPathname(), strlen($source) + 1)] ?? null) !== hash_file('sha256', $file->getPathname())) { + throw new RuntimeException('An exported resource changed or disappeared.'); + } +} +$metadata = json_decode(file_get_contents($package . '/composer.json'), true, flags: JSON_THROW_ON_ERROR); +$metadata['version'] = 'dev-fixture'; +$metadata['dist'] = ['type' => 'zip', 'url' => $root . '/git.zip']; +$metadata['source'] = ['type' => 'git', 'url' => $package, 'reference' => $reference]; +$repositories = [['type' => 'package', 'package' => $metadata], ['packagist.org' => false]]; +$results = []; +foreach (['local', 'global', 'source'] as $mode) { + $consumer = $root . '/' . $mode; + mkdir($consumer); + writeJson($consumer . '/composer.json', ['name' => 'fixture/consumer', 'repositories' => $repositories]); + $env = $environment; + $prefix = ['composer']; + if ('global' === $mode) { + $env['COMPOSER_HOME'] = $consumer; + $prefix[] = 'global'; + } + runCommand([...$prefix, 'require', 'source' === $mode ? '--prefer-source' : '--prefer-dist', '--no-scripts', '--no-plugins', '--no-interaction', 'fast-forward/code-standards:dev-fixture'], $consumer, environment: $env); + runCommand([...$prefix, 'install', '--no-dev', '--no-scripts', '--no-plugins', '--no-interaction'], $consumer, environment: $env); + $lock = json_decode(file_get_contents($consumer . '/composer.lock'), true, flags: JSON_THROW_ON_ERROR); + if (['fast-forward/code-standards'] !== array_column($lock['packages'], 'name') || ('source' === $mode) !== is_dir($consumer . '/vendor/fast-forward/code-standards/src')) { + throw new RuntimeException('Consumer received source or external runtime libraries.'); + } + $proxy = $consumer . '/vendor/bin/code-standards'; + if (!str_contains(file_get_contents($proxy), 'builds/code-standards')) { + throw new RuntimeException('Composer did not target the compiled executable directly.'); + } + runCommand([PHP_BINARY, $proxy, 'list', '--raw'], $consumer, environment: $env); + runCommand(['git', 'init', '-q'], $consumer, environment: $env); + file_put_contents($consumer . '/source.php', 'withPhpVersion(80300)->withoutParallel()->withCache(__DIR__."/.cache")->withPaths([__DIR__."/source.php"])->withRules([\\Rector\\Php54\\Rector\\Array_\\LongArrayToShortArrayRector::class]);'); + file_put_contents($consumer . '/ecs.php', <<<'CONFIG' +getFileName(), 3); +file_put_contents(__DIR__ . '/probe.json', json_encode(['runtime' => $runtime, 'mode' => fileperms($runtime) & 0777, 'identity' => json_decode(file_get_contents($runtime . '/distribution-manifest.json'), true)])); +return FastForward\CodeStandards\Config\ECSConfig::configure(); +CONFIG); + file_put_contents($consumer . '/composer.lock', '{"fixture":"untouched"}'); + runCommand(['git', 'add', 'source.php'], $consumer, environment: $env); + $inputs = ['source.php', 'policy.php', 'ecs.php', 'composer.json', 'composer.lock']; + $before = array_map(static fn(string $path) => hash_file('sha256', $consumer . '/' . $path), $inputs); + $check = runCommand([PHP_BINARY, $proxy, 'check', '--all', '--config=ecs.php'], $consumer, 2, $env); + $probe = json_decode(file_get_contents($consumer . '/probe.json'), true, flags: JSON_THROW_ON_ERROR); + if (hash_file('sha256', $source . '/composer.lock') !== $probe['identity']['composer_lock_sha256'] || inputHashes($source) !== $probe['identity']['source_files_sha256']) { + throw new RuntimeException('Compiled payload does not match the current source and lock.'); + } + if (buildHashes($source) !== ($probe['identity']['build_recipe_sha256'] ?? null)) { + throw new RuntimeException('Compiled payload does not match the current PHP build recipe.'); + } + if (hash_file('sha256', $source . '/composer.json') !== ($probe['identity']['composer_json_sha256'] ?? null)) { + throw new RuntimeException('Compiled payload does not match the current Composer build manifest.'); + } + if (0700 !== $probe['mode'] || file_exists($probe['runtime']) || 'fast-forward/code-standards' !== $probe['identity']['root']['name']) { + throw new RuntimeException('Private runtime identity or cleanup failed.'); + } + $rector = runCommand([PHP_BINARY, $proxy, 'refactor', '--config=policy.php'], $consumer, 2, $env); + $mnd = runCommand([PHP_BINARY, $proxy, 'magic-numbers', '--all'], $consumer, 1, $env); + $normalized = runCommand([PHP_BINARY, $proxy, 'normalize-composer'], $consumer, 1, $env); + if (!str_contains($normalized, 'composer.lock is not fresh')) { + throw new RuntimeException('Bundled Composer schema/lock diagnostic missing.'); + } + if (extension_loaded('dom')) { + $xml = runCommand([PHP_BINARY, $proxy, 'magic-numbers', '--all', '--format=xml'], $consumer, 1, $env); + $document = new DOMDocument(); + if (!$document->loadXML($xml) || (int) $document->documentElement->getAttribute('errorCount') < 1) { + throw new RuntimeException('Native XML report did not preserve findings.'); + } + } + $after = array_map(static fn(string $path) => hash_file('sha256', $consumer . '/' . $path), $inputs); + if (!str_contains($check, 'begin diff') || !str_contains($rector, 'LongArrayToShortArrayRector') || !str_contains($mnd, '42') || $before !== $after) { + throw new RuntimeException('Native runtime output or source preservation failed.'); + } + $fixedSource = null; + foreach (['fix', 'check', 'fix', 'check'] as $operation) { + runCommand([PHP_BINARY, $proxy, $operation, '--all', '--config=ecs.php'], $consumer, environment: $env); + $sourceHash = hash_file('sha256', $consumer . '/source.php'); + if (null !== $fixedSource && $sourceHash !== $fixedSource) { + throw new RuntimeException('Repeated clean check/fix changed formatted source.'); + } + $fixedSource = $sourceHash; + $repeatProbe = json_decode(file_get_contents($consumer . '/probe.json'), true, flags: JSON_THROW_ON_ERROR); + if (file_exists($repeatProbe['runtime']) || is_dir($root . '/temporary/fast-forward-code-standards')) { + throw new RuntimeException('Repeated compiled operations leaked a runtime or persistent default ECS cache.'); + } + } + file_put_contents($consumer . '/override.php', 'withCache(__DIR__ . "/explicit-cache");'); + runCommand([PHP_BINARY, $proxy, 'check', '--all', '--config=override.php'], $consumer, environment: $env); + if (!is_dir($consumer . '/explicit-cache')) { + throw new RuntimeException('Explicit consumer cache override was removed with the extracted runtime.'); + } + file_put_contents($consumer . '/vendor/autoload.php', ' array_column($lock['packages'], 'name'), + 'native_exits' => ['check' => 2, 'refactor' => 2, 'magic-numbers' => 1], + 'repeated_cache_operations' => ['fix', 'check', 'fix', 'check'], + 'default_cache_residue' => false, + 'explicit_cache_preserved' => true, + 'consumer_rules' => verifyConsumerRules($proxy, $env), + ]; +} +fwrite(STDOUT, json_encode(['php' => PHP_VERSION, 'bytes' => filesize($binary), 'sha256' => hash_file('sha256', $binary), 'results' => $results], JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR) . PHP_EOL); diff --git a/src/Config/ECSConfig.php b/src/Config/ECSConfig.php index 30749cb..512aef6 100644 --- a/src/Config/ECSConfig.php +++ b/src/Config/ECSConfig.php @@ -52,7 +52,7 @@ public static function configure(?callable $customize = null): ECSConfigBuilder return $config; } - /** Keeps each checkout and owned policy revision in its own temporary cache; native overrides still run last. */ + /** Source installations reuse temporary caches; extracted distributions keep caches within their exclusive lifetime. */ private static function cacheDirectory(): string { $directory = getcwd(); @@ -60,7 +60,12 @@ private static function cacheDirectory(): string throw new RuntimeException('Cannot determine the checkout directory for the ECS cache.'); } - return sys_get_temp_dir() . '/fast-forward-code-standards/' . hash('sha256', $directory) . '/' . self::policyFingerprint(); + $installation = dirname(__DIR__, 2); + $cacheRoot = is_file($installation . '/distribution-manifest.json') + ? $installation . '/.cache/ecs' + : sys_get_temp_dir() . '/fast-forward-code-standards'; + + return $cacheRoot . '/' . hash('sha256', $directory) . '/' . self::policyFingerprint(); } /** Includes both source bytes and installation paths so an unchanged consumer cannot retain an older owned rule result. */ diff --git a/tests/Automation/WorkflowTest.php b/tests/Automation/WorkflowTest.php index 262400d..bc64413 100644 --- a/tests/Automation/WorkflowTest.php +++ b/tests/Automation/WorkflowTest.php @@ -41,6 +41,7 @@ public function testExternalActionsUseVerifiedPublishedReferences(): void 'actions/create-github-app-token@v3.3.0', 'actions/github-script@v8', 'actions/checkout@v7', + 'actions/upload-artifact@v7', 'shivammathur/setup-php@v2', 'php-fast-forward/changelog/.github/workflows/changelog.yml@42bc58fbab55596ee03d480ebf71d2a3dfc48fb4', 'php-fast-forward/code-standards/.github/workflows/code-standards.yml@v0', @@ -59,6 +60,17 @@ public function testExternalActionsUseVerifiedPublishedReferences(): void } } + /** A candidate's self-reported manifest must not authorize its first execution. */ + public function testCommittedPharComparisonPrecedesRuntimeQualification(): void + { + $workflow = file_get_contents(dirname(__DIR__, 2) . '/.github/workflows/repository-ci.yml'); + $comparison = strpos($workflow, 'php scripts/phar/compare.php "$RUNNER_TEMP/code-standards" builds/code-standards'); + $execution = strpos($workflow, 'php scripts/phar/verify.php builds/code-standards'); + self::assertNotFalse($comparison); + self::assertNotFalse($execution); + self::assertLessThan($execution, $comparison); + } + /** Prevents silent GITHUB_TOKEN publication by keeping the App identity explicit and publication-only. */ public function testChangelogCallerSelectsTheCompatiblePublicationOnlyApp(): void { @@ -127,7 +139,7 @@ public function testPrWorkflowHasNoPrivilegedEventSecretOrUntrustedCustomRunner( self::assertStringContainsString('github.event.pull_request.head.sha', $workflow); self::assertStringContainsString('github.event.pull_request.base.sha', $workflow); self::assertSame(2, substr_count($workflow, 'persist-credentials: false')); - self::assertStringContainsString('--no-dev --no-plugins --no-scripts', $workflow); + self::assertStringContainsString('--no-plugins --no-scripts', $workflow); self::assertStringNotContainsString('secrets.', $workflow); self::assertStringNotContainsString('contents: write', $workflow); self::assertStringContainsString('[[ -n "$STANDARDS_REPOSITORY" && "$STANDARDS_HEAD_REPOSITORY" == "$STANDARDS_REPOSITORY" ]] || exit 1', $workflow); diff --git a/tests/Config/CacheLifetimeTest.php b/tests/Config/CacheLifetimeTest.php new file mode 100644 index 0000000..bf4d99e --- /dev/null +++ b/tests/Config/CacheLifetimeTest.php @@ -0,0 +1,94 @@ +directory = realpath(sys_get_temp_dir()) . '/standards-cache-' . bin2hex(random_bytes(8)); + (new Filesystem())->mkdir([$this->directory . '/consumer', $this->directory . '/temp']); + } + + protected function tearDown(): void + { + (new Filesystem())->remove($this->directory); + } + + /** Repeated source invocations reuse one cache, while policy and installation revisions stay separate. */ + public function testSourceCachesRemainStableAndInvalidateOwnedPolicy(): void + { + $first = $this->installation('source-a'); + $second = $this->installation('source-b'); + $cache = $this->cache($first); + self::assertStringStartsWith($this->directory . '/temp/fast-forward-code-standards/', $cache); + self::assertSame($cache, $this->cache($first)); + self::assertNotSame($cache, $this->cache($second)); + file_put_contents($first . '/src/Sniff/MultilineBooleanConditionSniff.php', "\n// Changed owned policy fixture.\n", FILE_APPEND); + self::assertNotSame($cache, $this->cache($first)); + } + + /** Two independently extracted versions cannot share a default cache or leak it outside their runtime trees. */ + public function testExtractedCachesBelongToTheirRuntimeAndPreserveExplicitOverrides(): void + { + $first = $this->installation('runtime-a'); + $second = $this->installation('runtime-b'); + file_put_contents($first . '/distribution-manifest.json', '{"version":"a"}'); + file_put_contents($second . '/distribution-manifest.json', '{"version":"b"}'); + $cache = $this->cache($first); + self::assertStringStartsWith($first . '/.cache/ecs/', $cache); + self::assertSame($cache, $this->cache($first)); + self::assertStringStartsWith($second . '/.cache/ecs/', $this->cache($second)); + self::assertNotSame($cache, $this->cache($second)); + self::assertNotSame($cache, $this->cache($this->installation('source'))); + self::assertSame($this->directory . '/explicit-cache', $this->cache($first, $this->directory . '/explicit-cache')); + } + + private function installation(string $name): string + { + $root = dirname(__DIR__, 2); + $installation = $this->directory . '/' . $name; + foreach (['Config/ECSConfig.php', 'Sniff/MultilineBooleanConditionSniff.php', 'Sniff/MultilineConditionSpacingSniff.php'] as $file) { + (new Filesystem())->mkdir(dirname($installation . '/src/' . $file)); + copy($root . '/src/' . $file, $installation . '/src/' . $file); + } + + return $installation; + } + + /** Reads the native public cache parameter, without inspecting this tool's private methods. */ + private function cache(string $installation, ?string $override = null): string + { + $probe = <<<'PHP' +require $argv[1]; +require dirname($argv[1]) . '/symplify/easy-coding-standard/vendor/squizlabs/php_codesniffer/autoload.php'; +require $argv[2] . '/src/Config/ECSConfig.php'; +require $argv[2] . '/src/Sniff/MultilineBooleanConditionSniff.php'; +require $argv[2] . '/src/Sniff/MultilineConditionSpacingSniff.php'; +$builder = FastForward\CodeStandards\Config\ECSConfig::configure( + isset($argv[3]) ? static fn($config) => $config->withCache($argv[3]) : null, +); +$builder(new Symplify\EasyCodingStandard\Config\ECSConfig()); +echo Symplify\EasyCodingStandard\DependencyInjection\SimpleParameterProvider::getStringParameter( + Symplify\EasyCodingStandard\ValueObject\Option::CACHE_DIRECTORY, +); +PHP; + $arguments = [PHP_BINARY, '-r', $probe, dirname(__DIR__, 2) . '/vendor/autoload.php', $installation]; + if (null !== $override) { + $arguments[] = $override; + } + $process = new Process($arguments, $this->directory . '/consumer', ['TMPDIR' => $this->directory . '/temp'], timeout: 30); + $process->mustRun(); + + return $process->getOutput(); + } +} diff --git a/tests/Distribution/ArchiveTest.php b/tests/Distribution/ArchiveTest.php index dcdfbd8..59a9bb5 100644 --- a/tests/Distribution/ArchiveTest.php +++ b/tests/Distribution/ArchiveTest.php @@ -22,9 +22,9 @@ protected function setUp(): void $root = dirname(__DIR__, 2); $paths = explode("\0", $this->execute(['git', 'ls-files', '-z'], $root)); $paths[] = '.gitattributes'; - $skillFiles = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root . '/resources/skills', \FilesystemIterator::SKIP_DOTS)); - foreach ($skillFiles as $file) { - self::assertFalse($file->isLink(), 'Canonical skills must be regular, portable files.'); + $resourceFiles = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root . '/resources', \FilesystemIterator::SKIP_DOTS)); + foreach ($resourceFiles as $file) { + self::assertFalse($file->isLink(), 'Canonical resources must be regular, portable files.'); if ($file->isFile()) { $paths[] = substr($file->getPathname(), strlen($root) + 1); } @@ -38,6 +38,8 @@ protected function setUp(): void self::assertIsString($content); $this->put($path, $content); } + $this->put('resources/templates/future-devtools/fixture.txt', 'copyable future resource'); + $this->put('builds/code-standards', 'synthetic compiled payload for archive selection'); $readme = file_get_contents($this->directory . '/README.md'); self::assertIsString($readme); $this->put('README.md', str_replace("\n", "\r\n", str_replace("\r\n", "\n", $readme))); @@ -74,12 +76,19 @@ public function testGitAndComposerExportTheSamePackageFiles(): void $git = $this->contents($this->directory . '/.build/archives/git.zip'); $composer = $this->contents($this->directory . '/.build/archives/composer.zip'); self::assertSame($git, $composer); - foreach (['bin/code-standards', 'config/ecs.php', 'composer.lock', 'README.md', 'CHANGELOG.md', 'LICENSE', + foreach (['builds/code-standards', 'composer.json', 'composer.lock', 'README.md', 'CHANGELOG.md', 'LICENSE', '.github/actions/check/action.yml', 'resources/workflows/code-standards.yml', 'resources/skills/fast-forward-code-standards/SKILL.md', 'resources/skills/fast-forward-code-standards/LICENSE', 'docs/_static/mascot-banner.png'] as $path) { self::assertArrayHasKey($path, $git); } + self::assertSame(hash('sha256', 'copyable future resource'), $git['resources/templates/future-devtools/fixture.txt']); + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(dirname(__DIR__, 2) . '/resources', \FilesystemIterator::SKIP_DOTS)) as $file) { + if ($file->isFile()) { + $path = substr($file->getPathname(), strlen(dirname(__DIR__, 2)) + 1); + self::assertSame(hash_file('sha256', $file->getPathname()), $git[$path] ?? null, $path); + } + } self::assertSame(hash_file('sha256', dirname(__DIR__, 2) . '/LICENSE'), $git['resources/skills/fast-forward-code-standards/LICENSE']); self::assertSame(hash_file('sha256', dirname(__DIR__, 2) . '/resources/skills/fast-forward-code-standards/SKILL.md'), $git['resources/skills/fast-forward-code-standards/SKILL.md']); foreach (['configuration-and-ci.md', 'optional-tools.md', 'installation.md'] as $name) { @@ -95,7 +104,7 @@ public function testGitAndComposerExportTheSamePackageFiles(): void } self::assertArrayNotHasKey('resources/skills/fast-forward-phpdoc/NOTICE', $git); foreach (array_keys($git) as $path) { - self::assertDoesNotMatchRegularExpression('~^(?:vendor|\.agents|\.build|\.phpunit\.cache|tests|\.github/workflows)/~', $path); + self::assertDoesNotMatchRegularExpression('~^(?:src|config|bin|scripts/phar|vendor|\.agents|\.build|\.phpunit\.cache|tests|\.github/workflows)/~', $path); } self::assertArrayNotHasKey('skills.json', $git); self::assertArrayNotHasKey('archive', json_decode(file_get_contents($this->directory . '/composer.json'), true, flags: JSON_THROW_ON_ERROR)); diff --git a/tests/Distribution/PharComparisonTest.php b/tests/Distribution/PharComparisonTest.php new file mode 100644 index 0000000..0966070 --- /dev/null +++ b/tests/Distribution/PharComparisonTest.php @@ -0,0 +1,98 @@ +directory = realpath(sys_get_temp_dir()) . '/standards-parity-' . bin2hex(random_bytes(8)); + mkdir($this->directory, 0700); + } + + protected function tearDown(): void + { + (new Filesystem())->remove($this->directory); + } + + public function testMatchingExecutablePayloadsPassWithoutExecutingEither(): void + { + $files = ['payload.php' => ' '{"same":"manifest"}']; + $stub = 'archive('fresh', $files, $stub); + $this->archive('committed', $files, $stub); + self::assertSame(0, $this->compare()->getExitCode()); + } + + #[DataProvider('changes')] + public function testChangedExecutableBytesAreRejectedBeforeExecution(string $change): void + { + $files = ['payload.php' => ' '{"same":"manifest"}']; + $stub = 'archive('fresh', $files, $stub); + if ('stub' === $change) { + $stub = 'directory . '/executed', true) . ', "executed"); __HALT_COMPILER();'; + } elseif ('file' === $change) { + $files['payload.php'] = 'archive('committed', $files, $stub); + $process = $this->compare(); + self::assertNotSame(0, $process->getExitCode()); + self::assertStringContainsString('from the fresh build', $process->getErrorOutput()); + self::assertFileDoesNotExist($this->directory . '/executed'); + } + + /** @return iterable */ + public static function changes(): iterable + { + foreach (['stub', 'file', 'extra', 'missing'] as $change) { + yield $change => [$change]; + } + } + + /** @param array $files */ + private function archive(string $name, array $files, string $stub): void + { + $builder = <<<'PHP' +$path = $argv[1]; +$archive = new Phar($path . '.phar'); +foreach (json_decode($argv[2], true, flags: JSON_THROW_ON_ERROR) as $name => $content) { + $archive[$name] = $content; +} +$archive->setStub($argv[3]); +$archive->setSignatureAlgorithm(Phar::SHA256); +unset($archive); +rename($path . '.phar', $path); +PHP; + $process = new Process([ + PHP_BINARY, '-d', 'phar.readonly=0', '-r', $builder, + $this->directory . '/' . $name, json_encode($files, JSON_THROW_ON_ERROR), $stub, + ], timeout: 30); + $process->mustRun(); + } + + private function compare(): Process + { + $process = new Process([ + PHP_BINARY, dirname(__DIR__, 2) . '/scripts/phar/compare.php', + $this->directory . '/fresh', $this->directory . '/committed', + ], timeout: 30); + $process->run(); + + return $process; + } +} diff --git a/tests/Integration/EcsBootstrapTest.php b/tests/Integration/EcsBootstrapTest.php index a26cd84..435ffc6 100644 --- a/tests/Integration/EcsBootstrapTest.php +++ b/tests/Integration/EcsBootstrapTest.php @@ -4,7 +4,6 @@ namespace FastForward\CodeStandards\Tests\Integration; -use PharData; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Filesystem\Filesystem; @@ -136,23 +135,16 @@ public function testConfigChangesInvalidateCacheAndErrorsFailClosed(): void self::assertSame(2, $this->cli(['check', '--all', '--config=missing.php'])->getExitCode()); } - /** The actual Composer archive contains an executable worker that needs only its own supplied runtime. */ - public function testComposerArchiveIncludesTheWorker(): void + /** The source worker needs only its supplied runtime; the PHAR harness tests archive execution. */ + public function testSourceWorkerUsesOnlyItsSuppliedRuntime(): void { $root = dirname(__DIR__, 2); $this->put('source.php', "put('vendor/autoload.php', 'git(['add', '--all']); - mkdir($this->directory . '/.build'); - $archive = new Process(['composer', 'archive', '--format=zip', '--dir=' . $this->directory . '/.build', '--file=package', '--no-plugins', '--no-scripts', '--no-interaction'], $root, timeout: 60); - self::assertSame(0, $archive->run(), $archive->getOutput() . $archive->getErrorOutput()); - $package = new PharData($this->directory . '/.build/package.zip'); - self::assertTrue(isset($package['bin/ecs-worker'])); - self::assertSame(file_get_contents($root . '/bin/ecs-worker'), $package['bin/ecs-worker']->getContent()); - $package->extractTo($this->directory . '/.build/package'); $worker = $this->worker([ - $this->directory . '/.build/package/config/ecs.php', '--check', '--', $this->directory . '/source.php', - ], $this->directory . '/.build/package/bin/ecs-worker'); + $root . '/config/ecs.php', '--check', '--', $this->directory . '/source.php', + ]); self::assertSame(0, $worker->getExitCode(), $worker->getOutput() . $worker->getErrorOutput()); self::assertFileDoesNotExist($this->directory . '/executed'); }