diff --git a/.changelog/compiled-composer-distribution.md b/.changelog/compiled-composer-distribution.md new file mode 100644 index 0000000..039eaea --- /dev/null +++ b/.changelog/compiled-composer-distribution.md @@ -0,0 +1,14 @@ +--- +category: added +type: minor +--- + +Distribute Code Standards as a ready-to-run PHAR for project and global Composer +installations, keeping bundled libraries out of the consumer dependency graph. +Preserve the complete resources and skills payload, support explicitly loaded +consumer ECS rules, and remove invocation-owned formatter caches at shutdown. +Build from the locked runtime and verify the complete executable against a fresh +build before execution. Compile with Box and a tracked configuration, keeping +the compiler isolated in CI. Refresh stale compiled executables through a separate +CI autocommit on unchanged same-repository PR heads. Use the compatible published Changelog workflow for +contribution checks and the existing release lifecycle. diff --git a/.gitattributes b/.gitattributes index 8f3cc00..8db0e16 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,7 @@ * text=auto *.php text eol=lf /bin/code-standards text eol=lf +/dist/code-standards binary # Local state and synchronized copies never belong to package distributions. /.git export-ignore @@ -12,13 +13,18 @@ # 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 /phpunit.xml export-ignore +/box.json export-ignore /.editorconfig export-ignore /.gitignore export-ignore /.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-changelog.yml b/.github/workflows/repository-changelog.yml index 32fec81..8b5d383 100644 --- a/.github/workflows/repository-changelog.yml +++ b/.github/workflows/repository-changelog.yml @@ -17,8 +17,8 @@ jobs: pull-requests: write issues: read statuses: write - # Reviewed integration candidate combines publication API and original-PR provenance fixes. - uses: php-fast-forward/changelog/.github/workflows/changelog.yml@42bc58fbab55596ee03d480ebf71d2a3dfc48fb4 + # Verified v0 currently supplies the publication-only App API. + uses: php-fast-forward/changelog/.github/workflows/changelog.yml@v0 with: runs-on: '["ubuntu-latest"]' base-branch: main diff --git a/.github/workflows/repository-ci.yml b/.github/workflows/repository-ci.yml index 53208c4..91a9673 100644 --- a/.github/workflows/repository-ci.yml +++ b/.github/workflows/repository-ci.yml @@ -17,13 +17,14 @@ jobs: steps: - uses: actions/checkout@v7 with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} persist-credentials: false - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: dom, posix coverage: none - tools: composer:v2 + tools: composer:v2, box:4.7.1 - run: composer install --no-plugins --no-scripts --no-interaction --prefer-dist --no-progress env: COMPOSER_HOME: ${{ runner.temp }}/standards-composer-home @@ -36,6 +37,34 @@ 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 scripts/phar/prepare.php "$RUNNER_TEMP/standards-runtime" + box validate "$RUNNER_TEMP/standards-runtime/box.json" + box compile --no-parallel --sort-compiled-files --config="$RUNNER_TEMP/standards-runtime/box.json" + install -m 755 "$RUNNER_TEMP/standards-runtime/dist/code-standards" "$RUNNER_TEMP/code-standards" + box verify "$RUNNER_TEMP/code-standards" + STANDARDS_PHAR="$RUNNER_TEMP/code-standards" vendor/bin/phpunit tests/Distribution/ArchiveTest.php --filter testCompiledDistributionRunsInDisposableConsumers + - name: Compare the committed executable before execution + id: parity + shell: bash + run: | + set +e + cmp "$RUNNER_TEMP/code-standards" dist/code-standards + status=$? + set -e + case "$status" in + 0) echo 'stale=false' >> "$GITHUB_OUTPUT" ;; + 1) echo 'stale=true' >> "$GITHUB_OUTPUT" ;; + *) exit "$status" ;; + esac + - name: Qualify the committed Composer distribution + if: steps.parity.outputs.stale == 'false' + run: | + STANDARDS_PHAR="$GITHUB_WORKSPACE/dist/code-standards" vendor/bin/phpunit tests/Distribution/ArchiveTest.php --filter testCompiledDistributionRunsInDisposableConsumers - 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: @@ -48,3 +77,16 @@ jobs: TWIG_EVALUATION_AUTOLOAD: ${{ runner.temp }}/standards-twig-vendor/autoload.php TWIG_EVALUATION_TEMP: ${{ runner.temp }} - run: git diff --exit-code + - name: Retain the reviewed PHP 8.3 build + if: matrix.php == '8.3' + uses: actions/upload-artifact@v7 + with: + name: code-standards-${{ steps.parity.outputs.stale == 'true' && 'update-' || '' }}${{ github.event.pull_request.head.sha || github.sha }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/code-standards + compression-level: 0 + if-no-files-found: error + - name: Require the compiled refresh to pass on its own commit + if: steps.parity.outputs.stale == 'true' + run: | + echo 'The committed executable is stale. The trusted refresh workflow may update this same-repository PR; CI must pass again on that commit.' >&2 + exit 1 diff --git a/.github/workflows/repository-phar-refresh.yml b/.github/workflows/repository-phar-refresh.yml new file mode 100644 index 0000000..4022123 --- /dev/null +++ b/.github/workflows/repository-phar-refresh.yml @@ -0,0 +1,58 @@ +name: 'Repository: Refresh compiled executable' +on: + workflow_run: + workflows: ['Repository: Quality'] + types: [completed] +permissions: + contents: read + actions: read +concurrency: + group: phar-refresh-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: false +jobs: + refresh: + if: >- + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'failure' && + github.event.workflow_run.head_repository.full_name == github.repository + runs-on: ubuntu-latest + timeout-minutes: 5 + environment: release-channels + steps: + - name: Download the qualified update, if one was produced + id: artifact + continue-on-error: true + uses: actions/download-artifact@v8.0.2 + with: + name: code-standards-update-${{ github.event.workflow_run.head_sha }}-${{ github.event.workflow_run.run_attempt }} + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ github.token }} + path: ${{ runner.temp }}/phar-update + - if: steps.artifact.outcome == 'success' + id: writer + uses: actions/create-github-app-token@v3.3.0 + with: + client-id: 'Iv23liHqRng02H747IYa' + private-key: ${{ secrets.RELEASE_CHANNEL_APP_PRIVATE_KEY }} + owner: php-fast-forward + repositories: code-standards + permission-contents: write + - if: steps.artifact.outcome == 'success' + uses: actions/checkout@v7 + with: + ref: ${{ github.event.workflow_run.head_sha }} + token: ${{ steps.writer.outputs.token }} + - if: steps.artifact.outcome == 'success' + run: | + test "$(git log -1 --format=%s)" != 'build: refresh compiled executable' + test -f dist/code-standards && test ! -L dist && test ! -L dist/code-standards + test -f "$RUNNER_TEMP/phar-update/code-standards" && test ! -L "$RUNNER_TEMP/phar-update/code-standards" + install -m 755 "$RUNNER_TEMP/phar-update/code-standards" dist/code-standards + - if: steps.artifact.outcome == 'success' + uses: stefanzweifel/git-auto-commit-action@v7.2.1 + with: + branch: ${{ github.event.workflow_run.head_branch }} + file_pattern: dist/code-standards + commit_message: 'build: refresh compiled executable' + skip_checkout: true + skip_fetch: true diff --git a/.github/workflows/repository-release-channels.yml b/.github/workflows/repository-release-channels.yml index e3252ea..799ce52 100644 --- a/.github/workflows/repository-release-channels.yml +++ b/.github/workflows/repository-release-channels.yml @@ -20,27 +20,29 @@ jobs: - name: Select the newest stable release for its major id: release env: + GH_TOKEN: ${{ github.token }} FF_RELEASE_TAG: ${{ github.event.release.tag_name }} - uses: actions/github-script@v9 - with: - script: | - const tag = process.env.FF_RELEASE_TAG; - const version = name => { - const match = /^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/.exec(name); - return match?.[0] === name ? match.slice(1).map(BigInt) : null; - }; - const selected = version(tag); - if (!selected) throw new Error('The release version cannot be compared.'); - const tags = await github.paginate(github.rest.repos.listTags, { ...context.repo, per_page: 100 }); - const compare = (left, right) => { - for (let index = 0; index < 3; ++index) { - if (left[index] !== right[index]) return left[index] > right[index] ? 1 : -1; + run: | + gh api --paginate "repos/$GITHUB_REPOSITORY/tags?per_page=100" --jq '.[].name' > "$RUNNER_TEMP/release-tags" + php <<'PHP' + ')) { + $latest = false; + if (explode('.', $other)[0] === $major) { + $promote = false; + } } - return 0; - }; - const stable = tags.map(item => version(item.name)).filter(Boolean); - core.setOutput('promote', !stable.some(item => item[0] === selected[0] && compare(item, selected) > 0)); - core.setOutput('latest', !stable.some(item => compare(item, selected) > 0)); + } + file_put_contents(getenv('GITHUB_OUTPUT'), 'promote=' . ($promote ? 'true' : 'false') . "\nlatest=" . ($latest ? 'true' : 'false') . "\n", FILE_APPEND); + PHP - name: Create the repository-scoped channel token if: steps.release.outputs.promote == 'true' id: channel-app @@ -55,7 +57,7 @@ jobs: skip-token-revoke: 'false' - name: Update the released major branch if: steps.release.outputs.promote == 'true' - uses: Actions-R-Us/actions-tagger@330ddfac760021349fef7ff62b372f2f691c20fb # v2.0.3 + uses: Actions-R-Us/actions-tagger@v2.0.3 with: token: ${{ steps.channel-app.outputs.token }} prefer_branch_releases: 'true' diff --git a/.gitignore b/.gitignore index e3f4124..d21714d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /vendor/ +/dist/*.phar /.build/ /.phpunit.cache/ diff --git a/AGENTS.md b/AGENTS.md index e7878b3..355c4d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,7 +58,7 @@ reusables under `.github/workflows/code-standards*.yml`, own event workflows use confirmed stable publications may promote their matching major channel. The actions-tagger branch/tag integration requires stable-version selection and approved publication/name-reservation setup; its mutation has no CAS. - GitHub channel refs are not Composer/CPX constraints. + GitHub channel refs are not Composer constraints. ## Work Guidance @@ -89,7 +89,7 @@ for the exact local base/head before publication; do not claim remote CI evidenc - [Legacy fixer comparison](docs/legacy-fixer-comparison.rst): fixed-source inventory, loaded dependencies and remaining profile decisions. - [PHPDoc comparison](docs/phpdoc-comparison.rst): actual custom-rule registration, source contracts and remaining golden decisions. - [Static-analysis evaluation](docs/static-analysis-evaluation.rst): candidate engines, whole-project analysis and unresolved domain ownership. -- [Architecture decision](docs/architecture.rst): library/tool dependencies, CPX/autoload limits and bootstrap without circular prerequisites. +- [Architecture decision](docs/architecture.rst): library/tool dependencies, runtime/autoload limits and bootstrap without circular prerequisites. - [Automation](docs/automation.rst): trust, ref/bootstrap contracts and consumer adoption. - [Instruction map](docs/instruction-map.rst): single root scope and skill ownership. - [Consumer skill](resources/skills/fast-forward-code-standards/SKILL.md): portable consumption procedure; no automatic installation or rollout. 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/box.json b/box.json new file mode 100644 index 0000000..c095662 --- /dev/null +++ b/box.json @@ -0,0 +1,23 @@ +{ + "main": false, + "output": "dist/code-standards", + "stub": "stub.php", + "directories": [ + "bin", + "config", + "resources", + "src", + "vendor" + ], + "files": [ + "LICENSE", + "composer.json", + "composer.lock" + ], + "dump-autoload": false, + "exclude-composer-files": false, + "check-requirements": false, + "compression": "GZ", + "algorithm": "SHA256", + "timestamp": "2020-01-01T00:00:00+00:00" +} diff --git a/composer.json b/composer.json index 58ce7e6..f4c95a3 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" + "dist/code-standards" ], "config": { "allow-plugins": { @@ -89,6 +95,23 @@ "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" } diff --git a/composer.lock b/composer.lock index f49bf2f..e0e5080 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", @@ -1233,6 +1234,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", @@ -1425,6 +1492,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", @@ -1498,6 +1637,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", @@ -1556,48 +1755,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" @@ -1721,29 +2038,43 @@ "time": "2026-11-03T12:14:22+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": { @@ -1762,389 +2093,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.4", + "name": "povils/phpmnd", + "version": "v3.6.1", "source": { "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "55acbd956d7e3907f7d21aab54edd68a310609e6" + "url": "https://github.com/povils/phpmnd.git", + "reference": "2002af50f627a03d94d01529a3eb02754a23578d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/55acbd956d7e3907f7d21aab54edd68a310609e6", - "reference": "55acbd956d7e3907f7d21aab54edd68a310609e6", + "url": "https://api.github.com/repos/povils/phpmnd/zipball/2002af50f627a03d94d01529a3eb02754a23578d", + "reference": "2002af50f627a03d94d01529a3eb02754a23578d", "shasum": "" }, "require": { - "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.3.5" + "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.4" - }, - "funding": [ + "authors": [ { - "url": "https://github.com/tomasvotruba", - "type": "github" + "name": "Povilas Susinskas", + "email": "povilassusinskas@gmail.com" } ], - "time": "2026-11-03T18:48:08+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.2", + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "4882a0459c88772e4afa3a73ee81b7824490aa9c" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/4882a0459c88772e4afa3a73ee81b7824490aa9c", - "reference": "4882a0459c88772e4afa3a73ee81b7824490aa9c", + "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/", @@ -2153,60 +2571,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.2" + "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-11-03T10:16:18+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/", @@ -2215,54 +2624,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/", @@ -2271,77 +2674,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.23", + "name": "react/promise", + "version": "v3.3.0", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "81957c2440c05c962fe8291f318de6e85f8db330" + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/81957c2440c05c962fe8291f318de6e85f8db330", - "reference": "81957c2440c05c962fe8291f318de6e85f8db330", + "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": [ @@ -2349,1368 +2727,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.23" + "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-11-03T21:47:07+00:00" + "time": "2025-08-19T18:57:03+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.8.1", + "name": "rector/rector", + "version": "2.8.4", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "feb1edf4e546af3b85c5caebe8359a20db230ca3" + "url": "https://github.com/rectorphp/rector.git", + "reference": "55acbd956d7e3907f7d21aab54edd68a310609e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/feb1edf4e546af3b85c5caebe8359a20db230ca3", - "reference": "feb1edf4e546af3b85c5caebe8359a20db230ca3", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/55acbd956d7e3907f7d21aab54edd68a310609e6", + "reference": "55acbd956d7e3907f7d21aab54edd68a310609e6", "shasum": "" }, "require": { - "php": ">=8.1" + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.3.5" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.8-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.8.1" + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.8.4" }, "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-09-16T14:15:10+00:00" + "time": "2026-11-03T18:48:08+00:00" }, { - "name": "symfony/filesystem", - "version": "v7.4.23", + "name": "sebastian/cli-parser", + "version": "4.2.1", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "53d609da66578dad5a39fe290e8f924ec8f05e93" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "7d05781b13f7dec9043a629a21d086ed74582a15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/53d609da66578dad5a39fe290e8f924ec8f05e93", - "reference": "53d609da66578dad5a39fe290e8f924ec8f05e93", + "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.23" + "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-11-04T08:27:22+00:00" + "time": "2026-05-17T05:29:34+00:00" }, { - "name": "symfony/finder", - "version": "v7.4.23", + "name": "sebastian/comparator", + "version": "7.1.8", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "c723f990aefe8c1053b18374faf4231eba84f518" + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "7c65c1e79836812819705b473a90c12399542485" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/c723f990aefe8c1053b18374faf4231eba84f518", - "reference": "c723f990aefe8c1053b18374faf4231eba84f518", + "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.23" - }, - "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-11-02T10:56:49+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.3", + "name": "sebastian/diff", + "version": "7.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "5a8f33c87aeecffce24e721b5064fab05f4becf8" + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5a8f33c87aeecffce24e721b5064fab05f4becf8", - "reference": "5a8f33c87aeecffce24e721b5064fab05f4becf8", + "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.3" + "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-10-15T09:18:53+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.3", + "name": "sebastian/exporter", + "version": "7.0.3", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "122889ed24dc000165bcbfd77ec412f15cbdfc6a" + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/122889ed24dc000165bcbfd77ec412f15cbdfc6a", - "reference": "122889ed24dc000165bcbfd77ec412f15cbdfc6a", + "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.3" + "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-10-28T09:35:49+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.22", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "a5a6912edbbfc4c98cd080bc4bbe37c8e7eeb20a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/a5a6912edbbfc4c98cd080bc4bbe37c8e7eeb20a", - "reference": "a5a6912edbbfc4c98cd080bc4bbe37c8e7eeb20a", - "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.22" - }, - "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-25T14:40:16+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v3.8.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "052852a96fd5e5ebdb617dba22031e41a3300fb5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/052852a96fd5e5ebdb617dba22031e41a3300fb5", - "reference": "052852a96fd5e5ebdb617dba22031e41a3300fb5", - "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.8-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.8.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-10-20T17:38:25+00:00" - }, - { - "name": "symfony/string", - "version": "v7.4.22", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "8dff7da3a56638f1d189c5368183294102de4ced" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/8dff7da3a56638f1d189c5368183294102de4ced", - "reference": "8dff7da3a56638f1d189c5368183294102de4ced", - "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.22" - }, - "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-23T16:51:52+00:00" - }, - { - "name": "symplify/easy-coding-standard", - "version": "13.3.14", - "source": { - "type": "git", - "url": "https://github.com/ecsphp/ecs.git", - "reference": "c6a379b5d5cce68c60d2dbd1211989bdf58eacaf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ecsphp/ecs/zipball/c6a379b5d5cce68c60d2dbd1211989bdf58eacaf", - "reference": "c6a379b5d5cce68c60d2dbd1211989bdf58eacaf", - "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.14" - }, - "time": "2026-11-05T16:53:58+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": [ @@ -3718,120 +3468,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": { @@ -3845,52 +3580,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/" @@ -3901,67 +3655,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": { @@ -3980,17 +3727,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": [ { @@ -4006,1295 +3748,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.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a248d1640ab059b075f53a2ef0f9856e864e06b5" + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "4882a0459c88772e4afa3a73ee81b7824490aa9c" }, "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/4882a0459c88772e4afa3a73ee81b7824490aa9c", + "reference": "4882a0459c88772e4afa3a73ee81b7824490aa9c", "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.2" }, "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-11-03T10:16:18+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.23", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "babab73b7ef9fae406f2eb3ee11323a61caa63d5" + "url": "https://github.com/symfony/console.git", + "reference": "81957c2440c05c962fe8291f318de6e85f8db330" }, "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/81957c2440c05c962fe8291f318de6e85f8db330", + "reference": "81957c2440c05c962fe8291f318de6e85f8db330", "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.23" }, "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-11-03T21:47:07+00:00" }, { - "name": "phpunit/phpunit", - "version": "12.5.40", + "name": "symfony/deprecation-contracts", + "version": "v3.8.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "76bd3198f16aeb375a4d5f3626471445aed0966e" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "feb1edf4e546af3b85c5caebe8359a20db230ca3" }, "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/feb1edf4e546af3b85c5caebe8359a20db230ca3", + "reference": "feb1edf4e546af3b85c5caebe8359a20db230ca3", "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.8-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.8.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-09-16T14:15:10+00:00" }, { - "name": "sebastian/cli-parser", - "version": "4.2.1", + "name": "symfony/filesystem", + "version": "v7.4.23", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "7d05781b13f7dec9043a629a21d086ed74582a15" + "url": "https://github.com/symfony/filesystem.git", + "reference": "53d609da66578dad5a39fe290e8f924ec8f05e93" }, "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/53d609da66578dad5a39fe290e8f924ec8f05e93", + "reference": "53d609da66578dad5a39fe290e8f924ec8f05e93", "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.23" }, "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-11-04T08:27:22+00:00" }, { - "name": "sebastian/comparator", - "version": "7.1.8", + "name": "symfony/finder", + "version": "v7.4.23", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "7c65c1e79836812819705b473a90c12399542485" + "url": "https://github.com/symfony/finder.git", + "reference": "c723f990aefe8c1053b18374faf4231eba84f518" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/7c65c1e79836812819705b473a90c12399542485", - "reference": "7c65c1e79836812819705b473a90c12399542485", + "url": "https://api.github.com/repos/symfony/finder/zipball/c723f990aefe8c1053b18374faf4231eba84f518", + "reference": "c723f990aefe8c1053b18374faf4231eba84f518", "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.23" }, "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-11-02T10:56:49+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.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "5a8f33c87aeecffce24e721b5064fab05f4becf8" }, "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/5a8f33c87aeecffce24e721b5064fab05f4becf8", + "reference": "5a8f33c87aeecffce24e721b5064fab05f4becf8", "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.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/diff", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-08-25T15:35:54+00:00" + "time": "2026-10-15T09:18:53+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.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "122889ed24dc000165bcbfd77ec412f15cbdfc6a" }, "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/122889ed24dc000165bcbfd77ec412f15cbdfc6a", + "reference": "122889ed24dc000165bcbfd77ec412f15cbdfc6a", "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.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/exporter", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-05-20T04:37:17+00:00" + "time": "2026-10-28T09:35:49+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.22", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" + "url": "https://github.com/symfony/process.git", + "reference": "a5a6912edbbfc4c98cd080bc4bbe37c8e7eeb20a" }, "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/a5a6912edbbfc4c98cd080bc4bbe37c8e7eeb20a", + "reference": "a5a6912edbbfc4c98cd080bc4bbe37c8e7eeb20a", "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.22" }, "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-25T14:40:16+00:00" }, { - "name": "sebastian/type", - "version": "6.0.4", + "name": "symfony/service-contracts", + "version": "v3.8.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "82ff822c2edc46724be9f7411d3163021f602773" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "052852a96fd5e5ebdb617dba22031e41a3300fb5" }, "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/052852a96fd5e5ebdb617dba22031e41a3300fb5", + "reference": "052852a96fd5e5ebdb617dba22031e41a3300fb5", "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.8-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.8.1" }, "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-10-20T17:38:25+00:00" }, { - "name": "sebastian/version", - "version": "6.0.1", + "name": "symfony/string", + "version": "v7.4.22", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "9e935a16187e14f39b0e79910dabd614972cdb26" + "url": "https://github.com/symfony/string.git", + "reference": "8dff7da3a56638f1d189c5368183294102de4ced" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/9e935a16187e14f39b0e79910dabd614972cdb26", - "reference": "9e935a16187e14f39b0e79910dabd614972cdb26", + "url": "https://api.github.com/repos/symfony/string/zipball/8dff7da3a56638f1d189c5368183294102de4ced", + "reference": "8dff7da3a56638f1d189c5368183294102de4ced", "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.22" }, "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-23T16:51:52+00:00" }, { - "name": "staabm/side-effects-detector", - "version": "1.0.5", + "name": "symplify/easy-coding-standard", + "version": "13.3.14", "source": { "type": "git", - "url": "https://github.com/staabm/side-effects-detector.git", - "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + "url": "https://github.com/ecsphp/ecs.git", + "reference": "c6a379b5d5cce68c60d2dbd1211989bdf58eacaf" }, "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/c6a379b5d5cce68c60d2dbd1211989bdf58eacaf", + "reference": "c6a379b5d5cce68c60d2dbd1211989bdf58eacaf", "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.14" }, - "funding": [ - { - "url": "https://github.com/staabm", - "type": "github" - } - ], - "time": "2024-10-20T05:08:20+00:00" + "time": "2026-11-05T16:53:58+00:00" }, { "name": "theseer/tokenizer", @@ -5345,6 +5285,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": [], @@ -5354,9 +5353,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/dist/code-standards b/dist/code-standards new file mode 100755 index 0000000..783a09e Binary files /dev/null and b/dist/code-standards differ 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..1362717 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 ``dist/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..8671cd1 100644 --- a/docs/automation.rst +++ b/docs/automation.rst @@ -72,9 +72,9 @@ immutable release inputs for Composer/Packagist. ``latest`` is not a numeric stable Composer version: the installed Composer version parser rejects it as a version, unlike ``v0`` and ``v0.4.2``. No step exists solely to make latest a branch. -The candidate workflow pins ``Actions-R-Us/actions-tagger`` to immutable commit -``330ddfac760021349fef7ff62b372f2f691c20fb`` with ``v2.0.3`` as its version comment. -The maintainer selected this pin after reviewing the moving-tag security finding. +The workflow selects ``Actions-R-Us/actions-tagger@v2.0.3`` by the maintainer's +version-reference policy. The tag was verified against +``330ddfac760021349fef7ff62b372f2f691c20fb``; the workflow uses the version, not that SHA. The published, non-draft, stable v2.0.3 release and its tag were verified on 2026-10-29; the tag resolved to that exact commit. The workflow runs on real ``release: published`` and ``release: edited`` events with @@ -84,9 +84,9 @@ returning actual branch names for branch queries. Alone, it force-downgrades ``v0`` when an older release is edited and can move ``latest`` backwards even after 1.x. Its latest ref is hardcoded as a tag in branch mode. -A short inline ``github-script`` selection step compensates for that behavior. -It parses concrete stable tags, paginates the tag catalog and compares their -three numeric version components. An incomparable event version fails closed; +A short inline PHP selection step compensates for that behavior. +GitHub CLI paginates the tag catalog; PHP's native ``version_compare`` compares +concrete stable versions. An incomparable event version fails closed; this parsing serves comparison rather than a second publication-format validator. The App token and action run only when that release is the newest stable full tag of its major. ``publish_latest_tag`` is true only when it is also the newest stable full tag globally. Therefore editing an older 0.x or 1.x release @@ -94,8 +94,8 @@ performs no writes; a later 0.x release after 1.x can advance v0 while leaving latest unchanged. A 1.x publication updates v1/latest without moving v0. No PHP updater, checkout, substituted event context or separate latest-ref writer remains. -Tests execute this actual inline selection and the byte-exact distributed bundle -with local API fixtures. They verify older-release no-ops, late 0.x versus global +PHPUnit executes this actual inline selection with local GitHub CLI fixtures. +It verifies older-release no-ops, late 0.x versus global latest, transition to 1.x and preservation of every concrete version tag. Tags such as v0/v1/latest are ignored by the stable-version catalog. An unpublished full version tag can conservatively block promotion; this is a tag catalog, not @@ -117,16 +117,18 @@ Publication-only App integration Code Standards v0.9.2 was published by ``github-actions[bot]`` through the old Changelog lifecycle's GITHUB_TOKEN fallback. That token cannot trigger the -separate release workflow. The caller now selects immutable candidate -``42bc58fbab55596ee03d480ebf71d2a3dfc48fb4`` combining Changelog PR76 and PR77 -in the review-only integration PR78. Its actual +separate release workflow. The caller selects Changelog's maintained ``v0`` +channel, verified at the published v0.5.0 revision +``5e093cd2080165ed2a3d9a18ae12899b7be94b46``. Its actual workflow API declares ``publish-app-client-id``, ``publish-environment`` and -``publish-app-private-key``. The existing published v0 revision +``publish-app-private-key``. The older published v0.4.1 revision ``203db95e68fc974e95ba1a921f08d01f88851be1`` does not declare them. The public Client ID is a non-empty literal, so an absent repository variable cannot silently select the job-token fallback. Only publication receives that -App selection and ``release-channels`` environment. The caller explicitly +App selection and ``release-channels`` environment within the Changelog lifecycle. +The separate compiled-executable refresh also uses this existing App, scoped to +one binary commit through the marketplace action; see :doc:`phar-distribution`. The caller explicitly forwards ``secrets.RELEASE_CHANNEL_APP_PRIVATE_KEY`` under the logical ``publish-app-private-key`` workflow-call name. The nested publication job declares ``RELEASE_CHANNEL_APP_PRIVATE_KEY`` so its environment supplies that same-named @@ -139,16 +141,12 @@ publication. Managed PR authorship remains ``github-actions[bot]``; the publishe App is not required to be that PR's author. No global App credential input or ``secrets: inherit`` is added, and the release-channel selector remains unchanged. -**This is a blocked draft integration, not an activated release path.** The SHA -is an inspectable PR candidate, not a published Changelog version. The compatible -adoption order is: +The published revision replaces the previous integration-candidate pin. Its +workflow declares the three inputs and routes publication credentials to the +nested publication job. This reference correction does not prove deployment +configuration or end-to-end publication. The remaining adoption gates are: -#. Review and qualify both upstream changes and the integrated candidate. - Let the owner merge/publish a compatible release. Verify that released revision actually - contains the three inputs and nested publication/authentication routing. -#. Replace this candidate pin with that verified published revision before stable - adoption, or obtain explicit acceptance of using the reviewed unreleased SHA. - Never send the new inputs to the old v0 and assume compatibility. +#. Qualify checks on the caller's actual HEAD using the published revision. #. Verify the separately authorized configuration owner's staged environment, valid secret name and protection rules through metadata; key validity and actual publication remain separate evidence. This caller patch does not @@ -194,7 +192,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..d13c510 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 ``dist/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 -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. +Bundled libraries live in ``require-dev``. CI prepares locked runtime roots in +a disposable directory and compiles with Box; contributors do not need Box. +The build never consumes Code Standards itself. Workflow tests additionally +need Bash and jq. 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..16c2105 --- /dev/null +++ b/docs/phar-distribution.rst @@ -0,0 +1,112 @@ +Compiled distribution +===================== + +Consumers install Code Standards through Composer and execute +``vendor/bin/code-standards``. The package points directly at the ready +``dist/code-standards`` PHAR; no consumer build or installer hook runs. +Contributors use the separate ``bin/code-standards`` source entrypoint. + +CI build +-------- + +CI installs the official Box 4.7.1 PHAR through ``setup-php``. Box is not a +Composer dependency of this project or its consumers. Developers do not need to +install it or commit a rebuilt executable manually. + +``scripts/phar/prepare.php`` has one purpose: prepare a disposable Composer +runtime from the existing lock. Tool libraries live in ``require-dev`` alongside +contributor tools, so their runtime roots must be selected explicitly from +``extra.phar.runtime-packages``. Composer resolves that closed set of locked +package records with plugins and scripts disabled. The checkout manifests, +installed vendor and consumer dependency graph are unchanged. + +The workflow then calls Box directly: + +.. code-block:: console + + php scripts/phar/prepare.php "$RUNNER_TEMP/standards-runtime" + box validate "$RUNNER_TEMP/standards-runtime/box.json" + box compile --no-parallel --sort-compiled-files --config="$RUNNER_TEMP/standards-runtime/box.json" + box verify "$RUNNER_TEMP/standards-runtime/dist/code-standards" + +``box.json`` owns assembly, GZ compression, SHA-256 signature, executable mode +and a fixed archive timestamp. Composer metadata, complete resources and +licenses are retained. The prepared autoloader is preserved. No PHP compactor +or namespace rewriting is enabled, so copied templates and configuration APIs +keep their original contents. + +GZ requires ext-zlib. The other platform requirements are declared in +``composer.json``; PHP-only portability is not claimed. The native Composer +platform check remains enabled. A SHA-256 PHAR signature checks integrity; it +is not publisher authentication. + +Runtime and consumer configuration +---------------------------------- + +Some bundled engines use physical entrypoints, realpath and subprocesses. +``scripts/phar/stub.php`` therefore extracts the runtime into a private 0700 +system-temporary directory and removes it at normal shutdown. Abrupt termination +can leave it behind. The consumer CWD and configuration paths are preserved. +Default ECS caches stay inside this disposable runtime; explicit native cache +overrides remain authoritative. This is process separation, not a namespace +sandbox. + +The default preset does not discover ``ecs.php`` or load the consumer autoloader +implicitly. Select project configuration explicitly: + +.. code-block:: console + + vendor/bin/code-standards check --all --config=ecs.php + +That PHP configuration can explicitly require its own ``vendor/autoload.php`` +and register project or development-package rules through the native ECS API. +Those rules must match the bundled engine APIs. Composer's development install +loads project ``autoload`` and ``autoload-dev`` mappings; ``--no-dev`` intentionally +omits development mappings/packages. See :doc:`api/configuration`. + +Validation and automatic refresh +-------------------------------- + +Quality runs on PHP 8.3, 8.4 and 8.5. Box validates its configuration and checks +the generated PHAR signature. PHPUnit exercises disposable Composer local, +global and source installs, exact resource preservation, native check/fix, +Rector and magic-number commands, consumer rule loading, CWD and cache cleanup. +These functional tests live in ``tests/Distribution`` and ``tests/Fixtures``; +they are not a second compiler or installer. + +Before parsing or executing the committed executable, CI compares every byte to +the fresh build with ``cmp``. A modified stub or forged embedded metadata cannot +authorize execution. Comparison read errors fail; differing bytes require a +refresh. Fixed timestamps and sorted entries make repeat builds reproducible in +the qualified CI environment, not every arbitrary toolchain. + +The PHP 8.3 job uploads its qualified build. A separate workflow accepts updates only +for same-repository PRs, downloads that run's artifact and invokes +``stefanzweifel/git-auto-commit-action`` for ``dist/code-standards`` only. +It does not execute project PHP or the downloaded PHAR. The existing repository +App supplies contents-write access so its commit triggers normal checks again. +The action pushes without force and does not pull or rebase concurrent changes. +An automatic refresh cannot repeatedly refresh its own commit. Forks retain +read-only checks. Missing update artifacts cause no commit. + +The existing ``release-channels`` environment rejects PR merge refs. The refresh +therefore uses GitHub's ``workflow_run`` event from the trusted default branch, +preserving that protection. Its definition must first reach the default branch +before the first live autocommit can be qualified. The initial committed PHAR +comes from the already-qualified PHP 8.3 CI artifact. + +Publication +----------- + +The PHAR is committed before the existing Changelog version-PR/tag lifecycle. +Packagist's standard GitHub archive therefore includes the ready executable. +A release asset alone would not replace that archive. No release or tag is +created by the build/refresh jobs, and a merge does not prove Packagist publication. + +``.gitattributes`` preserves the PHAR, Composer manifests, license and entire +``resources/`` tree, including skills and templates. Source and contributor +scripts/tests/configuration are excluded from dist archives. Executable reusable +workflows remain under ``.github/workflows``; copyable callers stay in resources. +``--prefer-source`` also includes development source but still runs the ready +binary. Source is maintained once; bundled library bytes and outer resources +are not deduplicated. diff --git a/docs/process-contracts.rst b/docs/process-contracts.rst index bf1a191..a0835cb 100644 --- a/docs/process-contracts.rst +++ b/docs/process-contracts.rst @@ -57,8 +57,8 @@ control characters inside diagnostic strings remain escaped JSON data. .. code-block:: console - cpx fast-forward/code-standards check --all --format=json - cpx fast-forward/code-standards check --all --format=json-pretty + vendor/bin/code-standards check --all --format=json + vendor/bin/code-standards check --all --format=json-pretty The schema contains ``schema_version``, ``command``, ``status``, ``exit_code``, ``complete``, selection coverage, native ECS ``files``, operational ``errors`` 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 fb9d681..87b2021 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..9f9de05 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 `dist/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/prepare.php b/scripts/phar/prepare.php new file mode 100644 index 0000000..d050c37 --- /dev/null +++ b/scripts/phar/prepare.php @@ -0,0 +1,51 @@ +mkdir($runtime, 0700); +foreach (['src', 'config', 'resources', 'bin'] as $path) { + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root . '/' . $path, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST) as $file) { + if ($file->isLink()) { + throw new RuntimeException('Symbolic build input: ' . $file->getPathname()); + } + } + $filesystem->mirror($root . '/' . $path, $runtime . '/' . $path); +} +foreach (['LICENSE', 'box.json'] as $path) { + $filesystem->copy($root . '/' . $path, $runtime . '/' . $path); +} +$filesystem->copy(__DIR__ . '/stub.php', $runtime . '/stub.php'); +$source = json_decode(file_get_contents($root . '/composer.json'), true, flags: JSON_THROW_ON_ERROR); +$lock = json_decode(file_get_contents($root . '/composer.lock'), 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], +]; +$filesystem->dumpFile($runtime . '/composer.json', json_encode($manifest, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR) . "\n"); +$process = new Process([ + PHP_BINARY, $root . '/vendor/composer/composer/bin/composer', 'update', + '--no-dev', '--no-plugins', '--no-scripts', '--no-interaction', '--prefer-dist', '--no-progress', '--no-audit', +], $runtime, ['COMPOSER' => false, 'COMPOSER_ROOT_VERSION' => false, 'COMPOSER_VENDOR_DIR' => false], timeout: 300); +$process->mustRun(); diff --git a/scripts/phar/stub.php b/scripts/phar/stub.php new file mode 100644 index 0000000..610bb83 --- /dev/null +++ b/scripts/phar/stub.php @@ -0,0 +1,32 @@ +#!/usr/bin/env php +isDir() && !$file->isLink()) { + rmdir($file->getPathname()); + } else { + unlink($file->getPathname()); + } + } + rmdir($runtime); +}); +(new Phar(__FILE__))->extractTo($runtime); +touch($runtime . '/.phar-runtime'); +$_composer_autoload_path = $runtime . '/vendor/autoload.php'; +require $runtime . '/bin/code-standards'; +__HALT_COMPILER(); diff --git a/src/Config/ECSConfig.php b/src/Config/ECSConfig.php index 30749cb..0ed00da 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 . '/.phar-runtime') + ? $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/ReleaseChannelGuardTest.cjs b/tests/Automation/ReleaseChannelGuardTest.cjs deleted file mode 100644 index 525c560..0000000 --- a/tests/Automation/ReleaseChannelGuardTest.cjs +++ /dev/null @@ -1,56 +0,0 @@ -const {test} = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); -const path = require('node:path'); - -const workflow = fs.readFileSync(path.join(__dirname, '../../.github/workflows/repository-release-channels.yml'), 'utf8'); -const body = workflow.split(' script: |\n')[1].split(' - name:')[0] - .trimEnd().split('\n').map(line => line.slice(12)).join('\n'); -const run = new (Object.getPrototypeOf(async function(){}).constructor)('github', 'context', 'core', body); -const sha = 'a'.repeat(40); - -async function select(tag, names) { - const outputs = {}; - const tags = names.map(name => ({name, commit: {sha}})); - const context = {repo: {owner: 'fixture', repo: 'repo'}, sha, payload: {release: {id: 1, tag_name: tag}}}; - const listTags = Symbol('listTags'); - const github = { - rest: {repos: { - listTags - }}, - paginate: async (method, args) => {assert.equal(method, listTags); assert.equal(args.per_page, 100); return tags;} - }; - process.env.FF_RELEASE_TAG = tag; - await run(github, context, {setOutput: (name, value) => {outputs[name] = value;}}); - return outputs; -} - -test('editing older releases never selects a major or latest update', async () => { - for (const [tag, tags] of [ - ['v0.4.1', ['v0.4.1', 'v0.4.2', 'v1.0.0']], - ['v1.0.0', ['v0.4.2', 'v1.0.0', 'v1.0.1']] - ]) assert.deepEqual(await select(tag, tags), {promote: false, latest: false}); -}); - -test('a later 0.x after 1.x selects only its major', async () => { - assert.deepEqual(await select('v0.4.3', ['v0.4.2', 'v0.4.3', 'v1.0.0']), {promote: true, latest: false}); -}); - -test('0.x to 1.x selects v1 and the default latest tag', async () => { - assert.deepEqual(await select('v1.0.0', ['v0.4.2', 'v1.0.0']), {promote: true, latest: true}); -}); - -test('version ordering is numeric and tolerates components beyond safe integers', async () => { - assert.deepEqual(await select('v0.10.0', ['v0.9.0', 'v0.10.0']), {promote: true, latest: true}); - assert.deepEqual(await select('v0.9007199254740992.0', ['v0.9007199254740992.0', 'v0.9007199254740993.0']), {promote: false, latest: false}); -}); - -test('moving major/latest names are not concrete stable versions', async () => { - assert.deepEqual(await select('v0.4.2', ['v0', 'v1', 'latest', 'v0.4.2']), {promote: true, latest: true}); -}); - -test('incomparable versions fail closed before promotion', async () => { - for (const name of ['v0', '0.4.2', 'v0.04.2', 'v0.4.2-rc1', 'v0.4.2+build', 'v0.4.2\n', '$(exit 0)']) { - await assert.rejects(select(name, [name])); - } -}); diff --git a/tests/Automation/WorkflowContextTest.php b/tests/Automation/WorkflowContextTest.php index d15b89f..b388916 100644 --- a/tests/Automation/WorkflowContextTest.php +++ b/tests/Automation/WorkflowContextTest.php @@ -59,8 +59,35 @@ public function testCheckGuardUsesExecutingWorkflowIdentity(): void /** Exercises the actual inline release selection without contacting GitHub or mutating refs. */ public function testReleaseSelectionPreventsOlderMajorAndGlobalPromotions(): void { - $process = new Process(['node', '--test', __DIR__ . '/ReleaseChannelGuardTest.cjs'], timeout: 20); - self::assertSame(0, $process->run(), $process->getOutput() . $process->getErrorOutput()); + $script = $this->guard('repository-release-channels.yml', 'Select the newest stable release for its major'); + $directory = sys_get_temp_dir() . '/release-selection-' . bin2hex(random_bytes(8)); + mkdir($directory, 0700); + file_put_contents($directory . '/gh', "#!/bin/sh\ncat \"\$FF_TAG_FIXTURE\"\n"); + chmod($directory . '/gh', 0755); + $environment = ['RUNNER_TEMP' => $directory, 'GITHUB_REPOSITORY' => 'fixture/repo', + 'FF_TAG_FIXTURE' => $directory . '/tags', 'PATH' => $directory . PATH_SEPARATOR . getenv('PATH')]; + try { + foreach ([ + ['v0.4.1', ['v0.4.1', 'v0.4.2', 'v1.0.0'], false, false], + ['v1.0.0', ['v1.0.0', 'v1.0.1'], false, false], + ['v0.4.3', ['v0.4.2', 'v0.4.3', 'v1.0.0'], true, false], + ['v1.0.0', ['v0.4.2', 'v1.0.0'], true, true], + ['v0.10.0', ['v0.9.0', 'v0.10.0'], true, true], + ['v0.4.2', ['v0', 'v1', 'latest', 'v0.4.2'], true, true], + ] as [$tag, $tags, $promote, $latest]) { + file_put_contents($directory . '/tags', implode("\n", $tags) . "\n"); + $result = $this->guardResult($script, [...$environment, 'FF_RELEASE_TAG' => $tag]); + self::assertSame(0, $result['status']); + self::assertSame('promote=' . ($promote ? 'true' : 'false') . "\nlatest=" . ($latest ? 'true' : 'false') . "\n", $result['output']); + } + foreach (['v0', '0.4.2', 'v0.04.2', 'v0.4.2-rc1', 'v0.4.2+build', "v0.4.2\n", '$(exit 0)'] as $tag) { + $result = $this->guardResult($script, [...$environment, 'FF_RELEASE_TAG' => $tag]); + self::assertNotSame(0, $result['status']); + self::assertSame('', $result['output']); + } + } finally { + (new \Symfony\Component\Filesystem\Filesystem())->remove($directory); + } } /** Rejects malformed or ambiguous native identity protocols before emitting any checkout output. */ diff --git a/tests/Automation/WorkflowTest.php b/tests/Automation/WorkflowTest.php index 006844f..0fff49d 100644 --- a/tests/Automation/WorkflowTest.php +++ b/tests/Automation/WorkflowTest.php @@ -5,6 +5,8 @@ namespace FastForward\CodeStandards\Tests\Automation; use PHPUnit\Framework\TestCase; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Process\Process; /** Contract assertions complement actionlint and real Git/CLI regressions. */ final class WorkflowTest extends TestCase @@ -37,12 +39,15 @@ public function testConsumerCallerDeclaresOnlyPullRequestsWithoutRemovingQuality public function testExternalActionsUseVerifiedPublishedReferences(): void { $references = [ - 'Actions-R-Us/actions-tagger@330ddfac760021349fef7ff62b372f2f691c20fb', + 'Actions-R-Us/actions-tagger@v2.0.3', 'actions/create-github-app-token@v3.3.0', 'actions/github-script@v9', 'actions/checkout@v7', + 'actions/upload-artifact@v7', + 'actions/download-artifact@v8.0.2', + 'stefanzweifel/git-auto-commit-action@v7.2.1', 'shivammathur/setup-php@v2', - 'php-fast-forward/changelog/.github/workflows/changelog.yml@42bc58fbab55596ee03d480ebf71d2a3dfc48fb4', + 'php-fast-forward/changelog/.github/workflows/changelog.yml@v0', 'php-fast-forward/code-standards/.github/workflows/code-standards.yml@v0', ]; $files = [ @@ -59,11 +64,69 @@ 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, 'cmp "$RUNNER_TEMP/code-standards" dist/code-standards'); + $execution = strpos($workflow, 'STANDARDS_PHAR="$GITHUB_WORKSPACE/dist/code-standards"'); + self::assertNotFalse($comparison); + self::assertNotFalse($execution); + self::assertLessThan($execution, $comparison); + } + + /** Runs the actual parity step against owned fixtures: differences authorize refresh, I/O errors do not. */ + public function testBinaryComparisonNeverExecutesCandidateBytesOrMasksReadErrors(): void + { + $workflow = file_get_contents(dirname(__DIR__, 2) . '/.github/workflows/repository-ci.yml'); + self::assertSame(1, preg_match('~id: parity\n shell: bash\n run: \|\n(.*?) - name:~s', $workflow, $matches)); + $script = preg_replace('/^ /m', '', $matches[1]); + $directory = sys_get_temp_dir() . '/standards-cmp-' . bin2hex(random_bytes(8)); + mkdir($directory . '/dist', 0700, true); + try { + file_put_contents($directory . '/code-standards', 'reviewed bytes'); + foreach (['reviewed bytes', ' $directory, + 'GITHUB_OUTPUT' => $directory . '/output', + ], timeout: 10); + $process->run(); + self::assertSame(null === $candidate ? 2 : 0, $process->getExitCode(), $process->getErrorOutput()); + self::assertSame(null === $candidate ? '' : ('reviewed bytes' === $candidate ? "stale=false\n" : "stale=true\n"), file_get_contents($directory . '/output')); + self::assertFileDoesNotExist($directory . '/executed'); + } + } finally { + (new Filesystem())->remove($directory); + } + } + + /** Keeps build execution separate from the narrowly scoped marketplace commit job. */ + public function testCompiledRefreshNeverExecutesPullRequestCodeWithTheWriter(): void + { + $workflow = file_get_contents(dirname(__DIR__, 2) . '/.github/workflows/repository-ci.yml'); + self::assertStringNotContainsString('secrets.', $workflow); + $refresh = file_get_contents(dirname(__DIR__, 2) . '/.github/workflows/repository-phar-refresh.yml'); + self::assertStringContainsString('github.event.workflow_run.head_repository.full_name == github.repository', $refresh); + self::assertStringContainsString('ref: ${{ github.event.workflow_run.head_sha }}', $refresh); + self::assertStringContainsString('run-id: ${{ github.event.workflow_run.id }}', $refresh); + self::assertStringContainsString('file_pattern: dist/code-standards', $refresh); + self::assertStringContainsString('stefanzweifel/git-auto-commit-action@v7.2.1', $refresh); + self::assertStringNotContainsString('php ', $refresh); + self::assertStringNotContainsString('permission-workflows:', $refresh); + self::assertStringNotContainsString('--force', $refresh); + } + /** Prevents silent GITHUB_TOKEN publication by keeping the App identity explicit and publication-only. */ public function testChangelogCallerSelectsTheCompatiblePublicationOnlyApp(): void { $workflow = file_get_contents(dirname(__DIR__, 2) . '/.github/workflows/repository-changelog.yml'); - self::assertStringContainsString('changelog.yml@42bc58fbab55596ee03d480ebf71d2a3dfc48fb4', $workflow); + self::assertStringContainsString('changelog.yml@v0', $workflow); self::assertStringContainsString("publish-app-client-id: 'Iv23liHqRng02H747IYa'", $workflow); self::assertStringContainsString('publish-environment: release-channels', $workflow); self::assertStringContainsString('managed-branch: changelog/version-recovery-98', $workflow); @@ -127,7 +190,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..b03f320 --- /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'); + touch($first . '/.phar-runtime'); + touch($second . '/.phar-runtime'); + $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..8a85806 100644 --- a/tests/Distribution/ArchiveTest.php +++ b/tests/Distribution/ArchiveTest.php @@ -14,6 +14,19 @@ final class ArchiveTest extends TestCase { private string $directory; + /** Runs the real installed CLI contract only when CI supplies a freshly compiled executable. */ + public function testCompiledDistributionRunsInDisposableConsumers(): void + { + $binary = getenv('STANDARDS_PHAR'); + if (false === $binary || '' === $binary) { + self::markTestSkipped('CI supplies STANDARDS_PHAR after Box compilation.'); + } + $process = new Process([PHP_BINARY, dirname(__DIR__) . '/Fixtures/phar-consumer.php', $binary], timeout: 600); + $process->mustRun(); + $result = json_decode($process->getOutput(), true, flags: JSON_THROW_ON_ERROR); + self::assertSame(['local', 'global', 'source'], array_keys($result['results'])); + } + /** Copies only owned tracked source into an isolated fixture, including candidate export attributes. */ protected function setUp(): void { @@ -22,9 +35,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 +51,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('dist/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 +89,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 (['dist/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,9 +117,10 @@ 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('box.json', $git); self::assertArrayNotHasKey('archive', json_decode(file_get_contents($this->directory . '/composer.json'), true, flags: JSON_THROW_ON_ERROR)); } diff --git a/tests/Fixtures/phar-consumer.php b/tests/Fixtures/phar-consumer.php new file mode 100644 index 0000000..ff9bac0 --- /dev/null +++ b/tests/Fixtures/phar-consumer.php @@ -0,0 +1,339 @@ + removeTree($root)); +copy($binary, $root . '/inspect.phar'); +$payload = new Phar($root . '/inspect.phar'); +foreach (inputHashes($source) as $path => $expected) { + if (!isset($payload[$path]) || hash('sha256', $payload[$path]->getContent()) !== $expected) { + throw new RuntimeException('Compiled source/resource bytes changed or disappeared: ' . $path); + } +} +$installed = json_decode($payload['vendor/composer/installed.json']->getContent(), true, flags: JSON_THROW_ON_ERROR); +$lock = json_decode(file_get_contents($source . '/composer.lock'), true, flags: JSON_THROW_ON_ERROR); +$locked = array_column(array_merge($lock['packages'], $lock['packages-dev']), null, 'name'); +if (false !== $installed['dev']) { + throw new RuntimeException('Compiled runtime contains development packages.'); +} +foreach ($installed['packages'] as $package) { + if (($locked[$package['name']]['version'] ?? null) !== $package['version'] || 'phpunit/phpunit' === $package['name']) { + throw new RuntimeException('Compiled runtime differs from the locked production graph.'); + } +} +unset($payload); +$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 . '/dist/code-standards'); +chmod($package . '/dist/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['dist/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), 'dist/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])); +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 (0700 !== $probe['mode'] || file_exists($probe['runtime'])) { + 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); + +/** Exercises real Composer autoload/autoload-dev and a local development package through the selected executable. */ +function verifyConsumerRules(string $executable, array $environment): array +{ + $directory = $environment['TMPDIR'] . '/consumer-rules-' . bin2hex(random_bytes(8)); + mkdir($directory, 0700); + register_shutdown_function(static fn() => 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]; +} + +/** Copies complete inputs, rejecting nonportable symbolic links. */ +function copyTree(string $source, string $target): void +{ + if (is_link($source)) { + throw new RuntimeException('Symbolic build input: ' . $source); + } + $filesystem = new Symfony\Component\Filesystem\Filesystem(); + if (is_dir($source)) { + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST) as $file) { + if ($file->isLink()) { + throw new RuntimeException('Symbolic build input: ' . $file->getPathname()); + } + } + $filesystem->mirror($source, $target); + + return; + } + $filesystem->copy($source, $target, true); +} + +/** Removes an exclusively owned tree without following links. */ +function removeTree(string $path): void +{ + (new Symfony\Component\Filesystem\Filesystem())->remove($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; +} + +/** + * @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/tests/Integration/EcsBootstrapTest.php b/tests/Integration/EcsBootstrapTest.php index a26cd84..cfb9dfd 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'); } @@ -190,7 +182,7 @@ public function testComposerBinaryProxyKeepsItsOwnInstallationAutoloader(): void public static function installationLayouts(): iterable { yield 'checkout' => ['.build/checkout']; - yield 'installed package called directly by CPX' => ['.build/runtime/vendor/fast-forward/code-standards']; + yield 'installed package called directly' => ['.build/runtime/vendor/fast-forward/code-standards']; } /** The entrypoint resolves its installation even when cwd contains an unrelated autoloader. */