diff --git a/.distrobox/Dockerfile b/.distrobox/Dockerfile index 81a2298..21063b5 100644 --- a/.distrobox/Dockerfile +++ b/.distrobox/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:10 +FROM debian:12 ENV LANG C.UTF-8 ENV LC_ALL C.UTF-8 @@ -32,8 +32,8 @@ RUN wget https://raw.githubusercontent.com/composer/getcomposer.org/master/web/i mv composer.phar /usr/local/bin/composer # Install PhpStorm -RUN wget -q https://download-cdn.jetbrains.com/webide/PhpStorm-2022.2.tar.gz -O /tmp/phpstorm.tar.gz && \ +RUN wget -q https://download-cdn.jetbrains.com/webide/PhpStorm-2022.3.1.tar.gz -O /tmp/phpstorm.tar.gz && \ mkdir /opt/phpstorm && \ tar -xzf /tmp/phpstorm.tar.gz -C /opt/phpstorm --strip-components=1 && \ - printf '#!/usr/bin/env bash\n/opt/phpstorm/bin/phpstorm.sh $@ &> /dev/null &' > /usr/local/bin/phpstorm && \ + printf '#!/usr/bin/env bash\nscreen -d -m bash -c "/opt/phpstorm/bin/phpstorm.sh $@"' > /usr/local/bin/phpstorm && \ chmod +x /usr/local/bin/phpstorm diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..bc3ab5c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..5e21b91 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,49 @@ +name: Release Docker images +on: + release: + types: [published] + +jobs: + publish: + strategy: + matrix: + php_version: + - "8.1" + - "8.2" + - "8.3" + - "8.4" + node_version: + - 16 + - 18 + - 20 + - 22 + - 24 + runs-on: ubuntu-latest + env: + PHP_VERSION: ${{ matrix.php_version }} + NODE_VERSION: ${{ matrix.node_version }} + LATEST_PHP_VERSION: 8.4 + LATEST_NODE_VERSION: 24 + steps: + - name: Checkout hypernode-deploy + uses: actions/checkout@v3 + - name: Prepare environment + run: | + export TAG_SPECS="php${{ matrix.php_version }}-node${{ matrix.node_version }}" + echo "TAG_SPECS=${TAG_SPECS}" >> $GITHUB_ENV + echo "DOCKER_TAG=quay.io/hypernode/deploy:${GITHUB_REF_NAME}-${TAG_SPECS}" >> $GITHUB_ENV + - name: Login to Quay + run: docker login -u "${QUAY_USER}" -p "${QUAY_TOKEN}" quay.io + env: + QUAY_USER: ${{ secrets.QUAY_USER }} + QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }} + - name: Build image + run: | + docker build -t "$DOCKER_TAG" -f "./ci/build/Dockerfile" \ + --build-arg PHP_VERSION=${{ matrix.php_version }} \ + --build-arg NODE_VERSION=${{ matrix.node_version }} \ + . + - name: Push image + run: docker push "$DOCKER_TAG" + - name: Push semantic versions + run: ci/release_semantic_versions.sh diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index dc94864..8af02f0 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -6,26 +6,41 @@ jobs: integration_test: strategy: matrix: - php_version: [7.4, 8.1] + php_version: [8.1, 8.2, 8.3, 8.4] + testsuite: [general, brancher] runs-on: ubuntu-latest steps: - - name: Checkout hypernode-deploy - uses: actions/checkout@v3 - - name: Run test script - run: MAGENTO_REPO=./magento2 ./runtests.sh - shell: bash - env: - PHP_VERSION: ${{ matrix.php_version }} + - name: Checkout hypernode-deploy + uses: actions/checkout@v3 + - name: Run general testsuite + if: ${{ matrix.testsuite == 'general' }} + run: MAGENTO_REPO=./magento2 ./runtests.sh general + shell: bash + env: + PHP_VERSION: ${{ matrix.php_version }} + - name: Start SSH agent for brancher testsuite + if: ${{ matrix.testsuite == 'brancher' && matrix.php_version == '8.4' }} + uses: webfactory/ssh-agent@v0.5.4 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Run brancher testsuite + if: ${{ matrix.testsuite == 'brancher' && matrix.php_version == '8.4' }} + run: ./runtests.sh brancher + shell: bash + env: + PHP_VERSION: ${{ matrix.php_version }} + HYPERNODE_API_TOKEN: ${{ secrets.HYPERNODE_API_TOKEN }} + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} code_quality: strategy: matrix: - php_version: [7.4, 8.1] + php_version: [8.1, 8.2, 8.3, 8.4] runs-on: ubuntu-latest steps: - name: Checkout hypernode-deploy uses: actions/checkout@v3 - name: Install PHP - uses: shivammathur/setup-php@2.21.1 + uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php_version }} tools: composer:v2 diff --git a/.gitignore b/.gitignore index 8044a40..2bd424f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ *.iml *.local.* *.swp + +# Tests +.phpunit.result.cache diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index ecff5c0..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,77 +0,0 @@ -image: docker:latest - -variables: - CONTAINER_IMAGE: git-registry.emico.nl/$CI_PROJECT_PATH - CONTAINER_IMAGE_HUB: hipex/deploy - DOCKER_DRIVER: overlay2 - COMPOSER_CACHE_DIR: composer-cache - -stages: - - quality - - build - - test - - release - - publish - -services: - - docker:dind - -# General extends -.default: - only: - - tags - - merge_requests - -include: - - '/gitlab/.gitlab-ci-variables-php.yml' - - '/gitlab/.gitlab-ci-variables-node.yml' - - '/gitlab/.gitlab-ci-build.yml' - - '/gitlab/.gitlab-ci-test.yml' - - '/gitlab/.gitlab-ci-publish.yml' - -# Code quality step -quality:dockerfile: - extends: .default - stage: quality - image: hadolint/hadolint:latest-debian - script: - - find . -type f -name "Dockerfile" | xargs --max-lines=1 hadolint --config=hadolint.yaml - -# Build include -.build: - extends: .default - stage: build - script: - - ./ci/build.sh - -# Test include -.test: - extends: .default - image: docker:latest - stage: test - before_script: - - apk add curl - - curl -LO https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64 && chmod +x container-structure-test-linux-amd64 && mkdir -p $HOME/bin && export PATH=$PATH:$HOME/bin && mv container-structure-test-linux-amd64 $HOME/bin/container-structure-test - script: - - ./ci/test.sh - -# Publish include -.publish: - extends: .default - stage: publish - only: - - tags - script: - - ./ci/publish.sh - -# Release step -release: - image: registry.hipex.cloud/hipex-services/release-cl - stage: release - script: - - semantic-release - only: - - master - - alpha - - beta - - next diff --git a/.releaserc b/.releaserc deleted file mode 100644 index c944358..0000000 --- a/.releaserc +++ /dev/null @@ -1,28 +0,0 @@ -{ - "plugins": [ - "@semantic-release/commit-analyzer", - "@semantic-release/release-notes-generator", - [ - "@semantic-release/gitlab", - { - "gitlabUrl": "https://git.emico.nl/" - } - ], - [ - "semantic-release-slack-bot", - { - "notifyOnSuccess": true, - "notifyOnFail": true - } - ], - [ - "@semantic-release/git", - { - "assets": [ - "docs/CHANGELOG.md" - ], - "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}" - } - ] - ] -} diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..691a79e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022-present Hypernode + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 47296dc..e942dcd 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,63 @@ -## Switching node version +## Hypernode Deploy -```bash -source /etc/profile.d/nvm.sh -nvm install -s stable -``` +This is the official application deployment tool for the Hypernode, a highly automated hosting platform for ecommerce applications. -## Tests +If you want to use Hypernode Deploy in your shop you will probably want to head over to the [hypernode-deploy-configuration repository](https://github.com/ByteInternet/hypernode-deploy-configuration) instead. -### Static tests -To run the static tests, please run the following commands: +If you are looking to see how the internals of the deployment tool work or even want to contribute a change to the project yourself, then you're in the right place. -```bash -composer --working-dir tools install -tools/vendor/bin/grumphp run --config tools/grumphp.yml -``` +This project builds the `hypernode/deploy` container image, which can be found on [quay.io/hypernode/deploy](https://quay.io/hypernode/deploy). + +## Updating deployer dependency + +Deployer is an integral part of Hypernode Deploy. +The official packagist distribution (and the git tags) are just phar distributions with all the engine code stripped away. +To properly install Deployer as a dependency, we install a very simple fork, which has git tags based on the engine code. + +Whenever a new Deployer version is released, here's the process: +- Sync the fork [ByteInternet/deployer](https://github.com/ByteInternet/deployer) +- Locate the commit for the release in the master branch. + - Usually this comes down to finding the commit that was pushed most recent to the release tag. +- Clone/update the fork locally. +- Run `git checkout ` +- Run `git tag v` +- Run `git push --tags` -### Docker container -We use Google Container Structure Tests over https://github.com/aelsabbahy/goss because the Hipex deploy container does not require a health check. +Finally, to utilize the new fork in Hypernode Deploy, you can simply change the deployer/deployer dependency in the `composer.json` file. -## Build images locally +## Building and running a local image + +If you don't want to use a pre-built image from https://quay.io/hypernode/deploy or if you are doing development on this project and you want to test out your changes, you can built an image locally and use that. ```bash -CONTAINER_IMAGE=hipex/deploy/dev \ -CI_COMMIT_TAG=2.0.2 \ -PHP_VERSION=7.4 \ -NODE_VERSION=14 \ -LOCAL_BUILD= \ -./ci/build.sh +export DOCKER_TAG=hypernode_deploy_dev:latest +export PHP_VERSION=8.2 +export NODE_VERSION=18 +docker build -t "$DOCKER_TAG" -f "./ci/build/Dockerfile" \ + --build-arg PHP_VERSION=$PHP_VERSION \ + --build-arg NODE_VERSION=$NODE_VERSION \ + . +``` + +This will give you a locally built image: +```console +$ docker images | grep hypernode_deploy_dev +localhost/hypernode_deploy_dev latest 5280aaef3a82 52 seconds ago 842 MB ``` -## Run with local image +That you could then use like: +```console +$ rm -Rf vendor +$ docker run --rm -it --env SSH_PRIVATE_KEY="$(cat ~/.ssh/yourdeploykey | base64)" -v ${PWD}:/build hypernode_deploy_dev:latest hypernode-deploy build -vvv +$ docker run --rm -it --env SSH_PRIVATE_KEY="$(cat ~/.ssh/yourdeploykey | base64)" -v ${PWD}:/build hypernode_deploy_dev:latest hypernode-deploy deploy staging -vvv +``` + +## Tests + +### Static tests +To run the static tests, please run the following commands: ```bash -rm -Rf vendor -docker run --rm -it --env SSH_PRIVATE_KEY="$(cat ~/.ssh/id_rsa_mydeploykey | base64)" -v ${PWD}:/build hipex/deploy/dev:2.1.0-php7.3-node13 hipex-deploy build -vvv +composer --working-dir tools install +tools/vendor/bin/grumphp run --config tools/grumphp.yml ``` diff --git a/bin/hypernode-deploy.php b/bin/hypernode-deploy.php index a2e73f7..49545ec 100644 --- a/bin/hypernode-deploy.php +++ b/bin/hypernode-deploy.php @@ -9,17 +9,17 @@ \define('WORKING_DIR', getcwd()); \define('APPLICATION_ROOT', \dirname(__DIR__)); -$customAutoLoadPath = WORKING_DIR . '/vendor/autoload.php'; // Allows to specify a custom autoload path to avoid overriding Hipex deploy packages // when the same packages are used by your application and Hipex Deploy. if (getenv('DEPLOY_AUTOLOAD_PATH') !== false) { $customAutoLoadPath = getenv('DEPLOY_AUTOLOAD_PATH'); -} -if (file_exists($customAutoLoadPath)) { - require_once $customAutoLoadPath; + if (file_exists($customAutoLoadPath)) { + require_once $customAutoLoadPath; + } } + /** @var \Composer\Autoload\ClassLoader $loader */ $loader = require_once APPLICATION_ROOT . '/vendor/autoload.php'; if (!array_key_exists('Deployer\\', $loader->getPrefixesPsr4())) { diff --git a/ci/build.sh b/ci/build.sh deleted file mode 100755 index f3398f7..0000000 --- a/ci/build.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env sh -set -e - -DEFAULT_PHP_VERSION="7.2" -DEFAULT_NODE_VERSION="12" - -echo "${CONTAINER_IMAGE}" - -cd "$(dirname "$0")/../" - -function build { - docker pull ${CONTAINER_IMAGE}:${2} || true - docker build -t ${CONTAINER_IMAGE}:${1} --build-arg PHP_VERSION=${PHP_VERSION} --build-arg NODE_VERSION=${NODE_VERSION} -f ./ci/build/Dockerfile . - - # Push image unless LOCAL_BUILD var is set - if [ -z "${LOCAL_BUILD+xxx}" ]; then - docker push ${CONTAINER_IMAGE}:${1} - fi -} - -if [ "$CI_JOB_TOKEN" ]; then - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN git-registry.emico.nl -fi - -if [ -z "$PHP_VERSION" ]; then - PHP_VERSION="$DEFAULT_PHP_VERSION" # PHP_VERSION is empty set default value -fi - -if [ -z "$NODE_VERSION" ]; then - NODE_VERSION="$DEFAULT_NODE_VERSION" # NODE_VERSION is empty set default value -fi - -IMAGE_NAME="php${PHP_VERSION}-node${NODE_VERSION}" -if [ "$CI_COMMIT_TAG" ]; then - build "${CI_COMMIT_TAG}-${IMAGE_NAME}" "${IMAGE_NAME}" -else - build "${IMAGE_NAME}" "${IMAGE_NAME}" -fi diff --git a/ci/build/Dockerfile b/ci/build/Dockerfile index 77be092..d870d20 100644 --- a/ci/build/Dockerfile +++ b/ci/build/Dockerfile @@ -1,43 +1,46 @@ -FROM debian:buster +FROM debian:bookworm-slim ARG NODE_VERSION ARG PHP_VERSION ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - git zip unzip wget \ - && rm -rf /var/lib/apt/lists/* +RUN apt-get update + +RUN apt-get install -y --no-install-recommends \ + git zip unzip wget # Add repositories -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - apt-transport-https \ - ca-certificates \ - curl \ - gnupg-agent \ - gnupg \ - software-properties-common \ - && rm -rf /var/lib/apt/lists/* \ - && curl -sSL http://debian.hypernode.com/repo.key | apt-key add - \ - && echo "deb http://debian.hypernode.com buster main hypernode" | tee /etc/apt/sources.list.d/hypernode.list \ - && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \ - && echo "deb http://deb.nodesource.com/node_${NODE_VERSION}.x buster main" | tee /etc/apt/sources.list.d/nodesource.list \ +RUN apt-get install -y --no-install-recommends \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg-agent \ + gnupg \ + software-properties-common \ + gettext-base + +RUN apt-get clean \ + && curl -fsSL http://debian.hypernode.com/repo.key | gpg --dearmor -o /usr/share/keyrings/hypernode.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/hypernode.gpg] http://debian.hypernode.com bookworm main hypernode" > /etc/apt/sources.list.d/hypernode.list \ && echo \ - "Package: * \ - Pin origin deb.nodesource.com \ - Pin-Priority: 1001" > /etc/apt/preferences.d/nodejs + "Package: * \ + Pin origin debian.hypernode.com \ + Pin-Priority: 1000" > /etc/apt/preferences.d/hypernode + +RUN curl -fsSL https://deb.nodesource.com/setup_$NODE_VERSION.x | bash - # Install dependencies -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ +RUN apt-get install -y --no-install-recommends \ openssh-client \ rsync \ git \ patch \ bash \ + jq \ ca-certificates \ + python3 \ + virtualenv \ wget \ curl \ openssl \ @@ -48,9 +51,12 @@ RUN apt-get update && \ nodejs \ gnupg \ zip \ - bc \ - && apt install -y --no-install-recommends \ + bc + +# Install PHP +RUN apt-get install -y --no-install-recommends \ php${PHP_VERSION} \ + php${PHP_VERSION}-amqp \ php${PHP_VERSION}-bcmath \ php${PHP_VERSION}-bz2 \ php${PHP_VERSION}-cli \ @@ -73,8 +79,10 @@ RUN apt-get update && \ php${PHP_VERSION}-soap \ php${PHP_VERSION}-tidy \ php${PHP_VERSION}-xml \ - php${PHP_VERSION}-zip \ - && rm -rf /var/lib/apt/lists/* + php${PHP_VERSION}-zip + +# Confirm NodeJS & NPM are installed +RUN node -v && npm -v COPY ./.git /hypernode/.git COPY ./bin /hypernode/bin @@ -86,8 +94,14 @@ COPY ./ci/build/files / RUN curl -sS https://getcomposer.org/installer | php \ && mv composer.phar /usr/local/bin/composer \ - && chmod +x /usr/local/bin/composer \ - && composer install --no-dev --optimize-autoloader --working-dir=/hypernode + && chmod +x /usr/local/bin/composer + +# Remove dev dependencies from composer.json +RUN cd /hypernode && \ + jq 'del(.["require-dev"])' composer.json > composer.tmp.json && \ + cp composer.tmp.json composer.json + +RUN composer install --no-dev --optimize-autoloader --working-dir=/hypernode --no-scripts --no-progress --no-interaction RUN bash /hypernode/ci/compile.sh @@ -103,6 +117,14 @@ RUN curl -sS https://getcomposer.org/installer | php -- --2.2 --filename=compose # Use version 1 for main composer binary RUN rm -f /usr/local/bin/composer; ln -s /usr/local/bin/composer2 /usr/local/bin/composer +# Install elgentos magento2-static-deploy binary for high-performance static content deployment +RUN curl -sL -o /opt/magento2-static-deploy \ + https://github.com/elgentos/magento2-static-deploy/releases/latest/download/magento2-static-deploy-linux-amd64 \ + && chmod +x /opt/magento2-static-deploy + +# Set python3 as default python executable +RUN ln -s /usr/bin/python3 /usr/local/bin/python + # Copy container files COPY ./ci/build/files / @@ -120,6 +142,9 @@ RUN rm -rvf \ /var/lib/apt/lists/* \ && apt-get autoremove -y +# Allow hypernode-deploy to be ran in ordinary git repository locations +RUN git config --global --add safe.directory "*" + # Setup default command CMD ["hypernode-deploy"] diff --git a/ci/container-structure-test.yaml b/ci/container-structure-test.yaml deleted file mode 100644 index 0d5d65e..0000000 --- a/ci/container-structure-test.yaml +++ /dev/null @@ -1,24 +0,0 @@ -schemaVersion: "2.0.0" - -commandTests: - - name: "Node Version" - command: "node" - args: - - "-v" - expectedOutput: [".*NODE_VERSION.*"] - - name: "Hipex deploy" - command: "hipex-deploy" - expectedOutput: [".*Build application and package.*"] - - name: "Composer" - command: "composer" - expectedOutput: [".*Composer version.*"] - - name: "PHP" - command: "php" - args: - - "-v" - expectedOutput: [".*PHP_VERSION.*"] - - name: "Docker Version" - command: "docker" - args: - - "-v" - expectedOutput: [ ".*Docker version.*" ] diff --git a/ci/hypernode-insecure-deploy-ci-key.pub b/ci/hypernode-insecure-deploy-ci-key.pub new file mode 100644 index 0000000..5e0a32e --- /dev/null +++ b/ci/hypernode-insecure-deploy-ci-key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDqf5tGbhjIbRQJ40bTvw69z0kUHQEeDtGkX5DTzXJ1IPHcFpbq+nxc3f++45V8FA3fq31xTjCR3+TWy/AGCVmuntVwJVCEaC/U3X0YDscmzAA4LEOfmMHIGnrJdeTJS4+N6L5uIlZqyBIeJXyEEgFpcHm4i1HGm13DR/XG7lTaXSkRI9IVb0i3S0VdrRjtqkg4M3ZjgNfAwmoDE3dGNwIBH5EVy8t/YNj/RH0TNdhTDL3AGwne6h2bTWb5SNLHNXvu2Wc5aiJlvn2E2W7mcpnPxEkVs1AeDEaPHcLaGE2+Bt/I4ntyjOy9pFDcf28sPKj76S3Sq0Wwdb8rYmT+lerIPfjwj1VPXTU1dhvDJ1ffTufp3Sn67qi7NfBMYayYxLJWh9q84RHgYhog2SNXM+autsTU40GDZUtDtORRZ+Rg5i1cGxUCRT+Fpcx3aFCM/yTLvU80qZkmfgkQwKnGCCFygDsXPuFbyRXRz9MI1+lYGg17ufW5HSHrAw34Rw2yd8k= hypernode-deploy-ci diff --git a/ci/publish.sh b/ci/publish.sh deleted file mode 100755 index d6fcb4a..0000000 --- a/ci/publish.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env sh -set -e - -DEFAULT_PHP_VERSION="7.1" -DEFAULT_NODE_VERSION="8" - -function deploy { - echo "This is output from build.sh" - - docker pull ${CONTAINER_IMAGE}:${1} - docker tag ${CONTAINER_IMAGE}:${1} ${CONTAINER_IMAGE_HUB}:${1} - docker push ${CONTAINER_IMAGE_HUB}:${1} -} - -docker login -u gitlab-ci-token -p $CI_JOB_TOKEN git-registry.emico.nl -docker login -u ${DOCKER_HUB_USERNAME} -p ${DOCKER_HUB_PASSWORD} - -if [ -z "$PHP_VERSION" ]; then - PHP_VERSION="$DEFAULT_PHP_VERSION" # PHP_VERSION is empty set default value -fi - -if [ -z "$NODE_VERSION" ]; then - NODE_VERSION="$DEFAULT_NODE_VERSION" # NODE_VERSION is empty set default value -fi - -IMAGE_NAME="php${PHP_VERSION}-node${NODE_VERSION}" -if [ "$CI_COMMIT_TAG" ]; then - deploy "${CI_COMMIT_TAG}-${IMAGE_NAME}" "${IMAGE_NAME}" -else - deploy "${IMAGE_NAME}" "${IMAGE_NAME}" - - # If PHP_VERSION and NODE_VERSION is default (latest) deploy without tag - if [ "$PHP_VERSION" == "$DEFAULT_PHP_VERSION" ] && [ "$NODE_VERSION" == "$DEFAULT_NODE_VERSION" ]; then - deploy - fi -fi diff --git a/ci/release_semantic_versions.sh b/ci/release_semantic_versions.sh index 76d5d50..b19aceb 100755 --- a/ci/release_semantic_versions.sh +++ b/ci/release_semantic_versions.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash IMAGE=${IMAGE:-quay.io/hypernode/deploy} -INPUT_VERSION=${INPUT_VERSION:-} +INPUT_VERSION=${GITHUB_REF_NAME} TAG_SPECS="php${PHP_VERSION}-node${NODE_VERSION}" -if [ ! -n "${INPUT_VERSION}" ]; then +if [ -z "${INPUT_VERSION}" ]; then echo "No input version provided, stopping". exit 1 fi @@ -21,6 +21,7 @@ function tag_and_publish () { docker push "${TARGET_TAG}" } +LOCAL_IMAGE_TAG="$IMAGE:$INPUT_VERSION-$TAG_SPECS" if echo "${INPUT_VERSION}" | grep -F "."; then MAJOR_VERSION=$(echo "${INPUT_VERSION}" | cut -d. -f1) MINOR_VERSION=$(echo "${INPUT_VERSION}" | cut -d. -f2) @@ -31,13 +32,17 @@ if echo "${INPUT_VERSION}" | grep -F "."; then #PATCH_SUFFIX=$(echo "${PATCH_VERSION}" | cut -d- -f2-) PATCH_VERSION=$(echo "${PATCH_VERSION}" | cut -d- -f1) fi - tag_and_publish "$IMAGE:$INPUT_VERSION-$TAG_SPECS" "$IMAGE:$MAJOR_VERSION.$MINOR_VERSION.$PATCH_VERSION-$TAG_SPECS" + tag_and_publish "$LOCAL_IMAGE_TAG" "$IMAGE:$MAJOR_VERSION.$MINOR_VERSION.$PATCH_VERSION-$TAG_SPECS" fi if [ -n "$MINOR_VERSION" ]; then - tag_and_publish "$IMAGE:$INPUT_VERSION-$TAG_SPECS" "$IMAGE:$MAJOR_VERSION.$MINOR_VERSION-$TAG_SPECS" + tag_and_publish "$LOCAL_IMAGE_TAG" "$IMAGE:$MAJOR_VERSION.$MINOR_VERSION-$TAG_SPECS" fi - tag_and_publish "$IMAGE:$INPUT_VERSION-$TAG_SPECS" "$IMAGE:$MAJOR_VERSION-$TAG_SPECS" + tag_and_publish "$LOCAL_IMAGE_TAG" "$IMAGE:$MAJOR_VERSION-$TAG_SPECS" + tag_and_publish "$LOCAL_IMAGE_TAG" "$IMAGE:latest-$TAG_SPECS" fi +if [[ "${PHP_VERSION}" == "${LATEST_PHP_VERSION}" ]] && [[ "${NODE_VERSION}" == "${LATEST_NODE_VERSION}" ]]; then + tag_and_publish "$LOCAL_IMAGE_TAG" "$IMAGE:latest" +fi diff --git a/ci/test.sh b/ci/test.sh deleted file mode 100755 index 7b6a24c..0000000 --- a/ci/test.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env sh -set -e - -docker login -u gitlab-ci-token -p $CI_JOB_TOKEN git-registry.emico.nl - -sed -i "s/PHP_VERSION/${PHP_VERSION}/g" ./ci/container-structure-test.yaml -sed -i "s/NODE_VERSION/${NODE_VERSION}/g" ./ci/container-structure-test.yaml - -if [ "$CI_COMMIT_TAG" ]; then - IMAGE="${CONTAINER_IMAGE}:${CI_COMMIT_TAG}-php${PHP_VERSION}-node${NODE_VERSION}" -else - IMAGE="${CONTAINER_IMAGE}:php${PHP_VERSION}-node${NODE_VERSION}" -fi - -docker pull ${IMAGE} -container-structure-test test --image ${IMAGE} --config ./ci/container-structure-test.yaml diff --git a/ci/test/magento/deploy1.php b/ci/test/magento/deploy1.php index a2747b8..46934fa 100644 --- a/ci/test/magento/deploy1.php +++ b/ci/test/magento/deploy1.php @@ -2,6 +2,9 @@ namespace Hypernode\DeployConfiguration; +use function Deployer\run; +use function Deployer\task; + /** * Start by setting up the configuration * @@ -10,6 +13,12 @@ */ $configuration = new ApplicationTemplate\Magento2(['en_US', 'nl_NL']); +task('debug:github:msg', static function () { + run('echo "::notice::This message should pass through to Github Workflow Summary!"'); +}); + +$configuration->addDeployTask('debug:github:msg'); + $productionStage = $configuration->addStage('production', 'banaan1.store'); $productionStage->addServer('hypernode', null, [], [ 'user' => 'app', diff --git a/ci/test/magento/deploy_brancher.php b/ci/test/magento/deploy_brancher.php new file mode 100644 index 0000000..5d9d3bd --- /dev/null +++ b/ci/test/magento/deploy_brancher.php @@ -0,0 +1,25 @@ +addStage('staging', 'banaan.store'); +$stagingStage->addServer('hndeployintegr8.hypernode.io', null, [], [ + 'user' => 'app', + 'port' => 22, +]); + +$productionStage = $configuration->addStage('test', 'banaan.store'); +$productionStage->addBrancherServer('hndeployintegr8') + ->setBrancherTimeout(3000) + ->setLabels(['gitref='. (\getenv('GITHUB_SHA') ?: 'unknown')]); + +return $configuration; diff --git a/ci/test/run-brancher.sh b/ci/test/run-brancher.sh new file mode 100755 index 0000000..352dfef --- /dev/null +++ b/ci/test/run-brancher.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +set -e +set -x + +# Handy aliases +HN="ssh app@hndeployintegr8.hypernode.io -o StrictHostKeyChecking=no" +DP="docker run --rm -v /tmp/m2build:/web -e HYPERNODE_API_TOKEN -e SSH_PRIVATE_KEY -e GITHUB_SHA -w /web hndeploy" + +# Build Docker image +docker build \ + -f ci/build/Dockerfile \ + --build-arg NODE_VERSION=22 \ + --build-arg PHP_VERSION="${PHP_VERSION:-8.2}" \ + -t hndeploy \ + . + +# Copy application from remote to local +$HN /data/web/magento2/bin/magento app:config:dump scopes themes +mkdir /tmp/m2build +mkdir -p "$HOME/.ssh" +cp ci/test/magento/deploy_brancher.php /tmp/m2build/deploy.php +rsync -a -e "ssh -o StrictHostKeyChecking=no" app@hndeployintegr8.hypernode.io:magento2/ /tmp/m2build +rm /tmp/m2build/app/etc/env.php +rm -rf /tmp/m2build/var/log + +# Build application +$DP hypernode-deploy build -f /web/deploy.php -vvv + +########################################## +# DEPLOY WITHOUT PLATFORM CONFIGURATIONS # +# This should pass, but not generate any # +# Nginx/Supervisor/etc configs # +########################################## +# SSH from deploy container to Hypernode container +$DP hypernode-deploy deploy staging -f /web/deploy.php -vvv + +# Run some tests, the staging environment should not have a brancher node +$DP ls -l +$DP test -f deployment-report.json +$DP jq . deployment-report.json +$DP jq .version deployment-report.json -r -e +$DP jq .stage deployment-report.json -r -e +$DP jq .hostnames[0] deployment-report.json -r -e +$DP jq '.brancher_hypernodes | select(length == 0)' deployment-report.json -r -e + +# Now do a test deploy which should have a brancher node. +$DP hypernode-deploy deploy test -f /web/deploy.php -vvv + +$DP ls -l +$DP test -f deployment-report.json +$DP jq . deployment-report.json +$DP jq .version deployment-report.json -r -e +$DP jq .stage deployment-report.json -r -e +BRANCHER_INSTANCE=$($DP jq .hostnames[0] deployment-report.json -r -e) +$DP jq .brancher_hypernodes[0] deployment-report.json -r -e + +# Run another test by reusing the last instance +$DP hypernode-deploy deploy test -f /web/deploy.php -vvv --reuse-brancher + +# Hostname of the reused Brancher instance should be the same as the previous one +$DP jq .hostnames[0] deployment-report.json -r -e | grep -F "${BRANCHER_INSTANCE}" + +# cleanup data +$DP hypernode-deploy cleanup -vvv + +rm -f deployment-report.json + +# Now do a test deploy again to deploy to a brancher node and clean it up by hnapi and labels matching +$DP hypernode-deploy deploy test -f /web/deploy.php -vvv + +$DP ls -l +$DP test -f deployment-report.json +$DP jq . deployment-report.json +$DP jq .version deployment-report.json -r -e +$DP jq .stage deployment-report.json -r -e +$DP jq .hostnames[0] deployment-report.json -r -e +$DP jq .brancher_hypernodes[0] deployment-report.json -r -e + +# Remove deployment report to make sure we can clean up using hnapi and labels matching +BRANCHER_INSTANCE=$($DP jq .brancher_hypernodes[0] deployment-report.json -r -e) +$DP rm -f deployment-report.json + +# cleanup data +$DP hypernode-deploy cleanup test -vvv | tee cleanup.log + +# Run tests on cleanup +grep -F "Cleaning up Brancher instances based on Hypernode hndeployintegr8 with labels [gitref=${GITHUB_SHA:-unknown}]" cleanup.log +grep "Stopping brancher Hypernode ${BRANCHER_INSTANCE}..." cleanup.log diff --git a/ci/test/run-general.sh b/ci/test/run-general.sh new file mode 100755 index 0000000..a82e1d0 --- /dev/null +++ b/ci/test/run-general.sh @@ -0,0 +1,193 @@ +#!/usr/bin/env bash + +set -e +set -x + +export PHP_VERSION_SHORT=$(echo "${PHP_VERSION:-8.2}" | sed 's/\.//') +if [[ "${PHP_VERSION:-8.2}" == "8.4" ]]; then + export IMAGE_OS="bookworm" +else + export IMAGE_OS="buster" +fi + +if [[ "${PHP_VERSION:-8.2}" == "8.1" ]]; then + export MAGENTO_VERSION="2.4.6-p10" +else + export MAGENTO_VERSION="2.4.8" +fi + +# Handy aliases +HN="docker-compose exec -T hypernode" +DP="docker-compose exec -e GITHUB_WORKFLOW -T deploy" +DP1="docker-compose exec -e GITHUB_WORKFLOW --workdir=/web1 -T deploy" +DP2="docker-compose exec -e GITHUB_WORKFLOW --workdir=/web2 -T deploy" + +function install_magento() { + $HN mysql -e "DROP DATABASE IF EXISTS dummytag_preinstalled_magento" + $HN mysql -e "CREATE DATABASE dummytag_preinstalled_magento" + local pw=$($HN bash -c "grep password /data/web/.my.cnf | cut -d' ' -f3") + + # Strip carriage return of pw and saves it in a new variable + pw=$(echo $pw | tr -d '\r') + + $HN bash -c "/data/web/magento2/bin/magento setup:install \ + --base-url=http://banaan1.store \ + --db-host=mysqlmaster.dummytag.hypernode.io \ + --db-name=dummytag_preinstalled_magento --db-user=app \ + --db-password=$pw \ + --admin-firstname=admin --admin-lastname=admin \ + --admin-email=admin@admin.com --admin-user=admin \ + --admin-password=admin123 --language=en_US --currency=USD \ + --timezone=America/Chicago --elasticsearch-host=localhost" +} + +function begin_task() { + if [[ -n "${GITHUB_WORKFLOW}" ]]; then + echo "::group::$@" + else + echo "$@" + fi +} + +function end_task() { + if [[ -n "${GITHUB_WORKFLOW}" ]]; then + echo "::endgroup::" + fi +} + +# Install docker-compose if it's not installed +if ! [ -x "$(command -v docker-compose)" ]; then + curl -sSL https://github.com/docker/compose/releases/download/v2.29.7/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose +fi + +# Clear up env +trap "docker-compose down -v" EXIT + +begin_task "Setting up Docker stack" +docker-compose up -d +end_task + +begin_task "Setting Magento 2" +# Create working initial Magento install on the Hypernode container +$HN composer create-project --repository=https://mirror.mage-os.org/ magento/project-community-edition:${MAGENTO_VERSION:-2.4.8} /data/web/magento2 +echo "Waiting for MySQL to be available on the Hypernode container" +$HN bash -c "until mysql -e 'select 1' ; do sleep 1; done" +install_magento +end_task + +# Copy env to the deploy container +$HN /data/web/magento2/bin/magento app:config:dump scopes themes +echo "Waiting for SSH to be available on the Hypernode container" +chmod 0600 ci/test/.ssh/id_rsa +chmod 0600 ci/test/.ssh/authorized_keys +$DP rsync -a app@hypernode:/data/web/magento2/ /web +$DP rsync -a /config/ /web +$DP rm /web/app/etc/env.php + +# Create second app +$DP cp -ra /web /web1 +$DP cp -ra /web /web2 + +# Build both apps +$DP1 hypernode-deploy build -v -f /web1/deploy1.php +$DP2 hypernode-deploy build -v -f /web2/deploy2.php + +# Prepare env +$HN mkdir -p /data/web/apps/banaan1.store/shared/app/etc/ +$HN cp /data/web/magento2/app/etc/env.php /data/web/apps/banaan1.store/shared/app/etc/env.php +$HN mkdir -p /data/web/apps/banaan2.store/shared/app/etc/ +$HN cp /data/web/magento2/app/etc/env.php /data/web/apps/banaan2.store/shared/app/etc/env.php +$HN chown -R app:app /data/web/apps + +########################################## +# DEPLOY WITHOUT PLATFORM CONFIGURATIONS # +# This should pass, but not generate any # +# Nginx/Supervisor/etc configs # +########################################## +# SSH from deploy container to hypernode container +$DP1 hypernode-deploy deploy production -f /web1/deploy1_without_platformconfig.php -v + +# Check if deployment made only one release for store1 +test $($HN ls /data/web/apps/banaan1.store/releases/ | wc -l) = 1 + +# Platform configs shouldn't be present yet +$HN test ! -d /data/web/nginx/banaan1.store +$HN test ! -d /data/web/supervisor/banaan1.store +$HN crontab -l -u app | grep "### BEGIN banaan1.store ###" && exit 1 +$HN test ! -d /data/web/varnish/banaan1.store + +################## +# DEPLOY STORE 2 # +################## +# Store 2 +$DP2 hypernode-deploy deploy production -f /web2/deploy2.php -v + +# Check if deployment made only one release for store2 +test $($HN ls /data/web/apps/banaan2.store/releases/ | wc -l) = 1 +$HN ls -al /data/web/nginx/banaan2.store/ +$HN ls -al /data/web/apps/banaan2.store/current/ +$HN ls -al /data/web/apps/banaan2.store/current/.hypernode/nginx/ +$HN test -f /data/web/nginx/banaan2.store/server.example.conf || ($HN ls -al /data/web/nginx && $HN ls -al /data/web/nginx/banaan2.store && exit 1) +$HN test $($HN readlink -f /data/web/nginx/banaan2.store) = /data/web/apps/banaan2.store/releases/1/.hypernode/nginx + +################################## +# DEPLOY PLATFORM CONFIGURATIONS # +# Now we should get revisions of # +# all platform configs. # +################################## +$DP1 hypernode-deploy deploy production -v -f /web1/deploy1.php + +# Check if example location block was placed +$HN ls -al /data/web/nginx/banaan1.store/ +$HN ls -al /data/web/apps/banaan1.store/current/ +$HN ls -al /data/web/apps/banaan1.store/current/.hypernode/nginx/ +$HN test -f /data/web/nginx/banaan1.store/server.example.conf || ($HN ls -al /data/web/nginx && $HN ls -al /data/web/nginx/banaan1.store && exit 1) +$HN test $($HN readlink -f /data/web/nginx/banaan1.store) = /data/web/apps/banaan1.store/releases/2/.hypernode/nginx + +$HN test -f /data/web/supervisor/banaan1.store/example.conf || ($HN ls -al /data/web/supervisor/ && exit 1) +$HN test $($HN readlink -f /data/web/supervisor/banaan1.store) = /data/web/apps/banaan1.store/releases/2/.hypernode/supervisor + +# Test this once we enable supervisor in the hypernode docker image +# $HN supervisorctl status | grep example | grep -v FATAL || ($HN supervisorctl status && exit 1) + +# Test if varnish dirs exists and vcl has been placed +$HN ls -al /data/web/varnish/banaan1.store/ +$HN ls -al /data/web/apps/banaan1.store/current/.hypernode/varnish/ + +$HN test -f /data/web/varnish/banaan1.store/varnish.vcl || ($HN ls -al /data/web/varnish/ && exit 1) +$HN test $($HN readlink -f /data/web/varnish/banaan1.store/varnish.vcl) = /data/web/apps/banaan1.store/releases/2/.hypernode/varnish/varnish.vcl + +# Check the content of the crontab block +$HN crontab -l -u app | grep "### BEGIN banaan1.store ###" +$HN crontab -l -u app | grep "### END banaan1.store ###" +$HN crontab -l -u app | sed -n -e '/### BEGIN banaan1.store ###/,/### END banaan1.store ###/ p' | grep "banaan" + +###################################### +# REMOVE A NGINX LOCATION # +# Create a new release but make sure # +# that the file is removed in the # +# new release. # +###################################### +# Remove example location +$DP rm /web1/etc/nginx/server.example.conf + +# Deploy again +$DP1 hypernode-deploy deploy production -f /web1/deploy1.php + +# Check if another deployment was made +test $($HN ls /data/web/apps/banaan1.store/releases/ | wc -l) = 3 +$HN test $($HN readlink -f /data/web/nginx/banaan1.store) = /data/web/apps/banaan1.store/releases/3/.hypernode/nginx +$HN test $($HN readlink -f /data/web/supervisor/banaan1.store) = /data/web/apps/banaan1.store/releases/3/.hypernode/supervisor +$HN test $($HN readlink -f /data/web/varnish/banaan1.store/varnish.vcl) = /data/web/apps/banaan1.store/releases/3/.hypernode/varnish/varnish.vcl + +# Verify example location block is removed +$HN test ! -f /data/web/nginx/banaan1.store/server.example.conf || ($HN ls -al /data/web/nginx/banaan1.store && exit 1) + +# Check if the second application is still working as intended +test $($HN ls /data/web/apps/banaan2.store/releases/ | wc -l) = 1 +$HN ls -al /data/web/nginx/banaan2.store/ +$HN ls -al /data/web/apps/banaan2.store/current/ +$HN ls -al /data/web/apps/banaan2.store/current/.hypernode/nginx/ +$HN test -f /data/web/nginx/banaan2.store/server.example.conf || ($HN ls -al /data/web/nginx && $HN ls -al /data/web/nginx/banaan2.store && exit 1) +$HN test $($HN readlink -f /data/web/nginx/banaan2.store) = /data/web/apps/banaan2.store/releases/1/.hypernode/nginx diff --git a/composer.json b/composer.json index b508aae..199284d 100644 --- a/composer.json +++ b/composer.json @@ -2,25 +2,29 @@ "name": "hypernode/deploy", "description": "Hypernode Deploy", "type": "application", - "license": "proprietary", + "license": "MIT", "bin": [ "bin/hypernode-deploy.php", "bin/hypernode-deploy" ], "require": { + "php": ">=8.1", "ext-json": "*", + "ext-pcntl": "*", "ext-zlib": "*", "composer-runtime-api": "^2", - "deployer/deployer": "dev-master#ff3c33c as v7.0.0", + "deployer/deployer": "v7.4.0", "doctrine/annotations": "^1.6", - "hypernode/deploy-configuration": "v2.x-dev", - "php-di/php-di": "^6.0", + "guzzlehttp/guzzle": "^7.5", + "hypernode/api-client": "^0.5", + "hypernode/deploy-configuration": "^3.6", + "php-di/php-di": "^7.0", "psr/log": "^1.0", "symfony/console": "^5.4", "symfony/finder": "^5.4", "symfony/http-client": "^5.4", "symfony/process": "^5.4", - "twig/twig": "^2.12", + "twig/twig": "^3.11.2", "webmozart/assert": "^1.11" }, "autoload": { @@ -31,12 +35,16 @@ "src/Deployer/functions.php" ] }, + "autoload-dev": { + "psr-4": { + "Hypernode\\Deploy\\Tests\\": "tests/" + } + }, "require-dev": { "phpro/grumphp-shim": "^1.13", - "phpunit/phpunit": "^8.5", - "roave/security-advisories": "dev-master", + "phpunit/phpunit": "^9.5", "squizlabs/php_codesniffer": "^3.7", - "vimeo/psalm": "^4.26" + "vimeo/psalm": "^6.0" }, "config": { "preferred-install": { @@ -44,7 +52,14 @@ }, "sort-packages": true, "allow-plugins": { - "phpro/grumphp-shim": true + "phpro/grumphp-shim": true, + "php-http/discovery": true + } + }, + "repositories": { + "deployer-fork": { + "type": "vcs", + "url": "https://github.com/ByteInternet/deployer" } } } diff --git a/composer.lock b/composer.lock index 4b3994c..651e2fe 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,155 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ca2b95539380ce8daf860ee14d6ac431", + "content-hash": "060108911b9a3452b54a2d0704c7c549", "packages": [ + { + "name": "carbonphp/carbon-doctrine-types", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-02-09T16:56:22+00:00" + }, + { + "name": "clue/stream-filter", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/clue/stream-filter.git", + "reference": "049509fef80032cb3f051595029ab75b49a3c2f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/stream-filter/zipball/049509fef80032cb3f051595029ab75b49a3c2f7", + "reference": "049509fef80032cb3f051595029ab75b49a3c2f7", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "Clue\\StreamFilter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "A simple and modern approach to stream filtering in PHP", + "homepage": "https://github.com/clue/stream-filter", + "keywords": [ + "bucket brigade", + "callback", + "filter", + "php_user_filter", + "stream", + "stream_filter_append", + "stream_filter_register" + ], + "support": { + "issues": "https://github.com/clue/stream-filter/issues", + "source": "https://github.com/clue/stream-filter/tree/v1.7.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2023-12-20T15:40:13+00:00" + }, { "name": "deployer/deployer", - "version": "dev-master", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/deployphp/deployer.git", - "reference": "ff3c33c" + "url": "https://github.com/ByteInternet/deployer.git", + "reference": "a3bab59d448de84099e52fc5b6c652737a8c2942" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/deployphp/deployer/zipball/ff3c33c", - "reference": "ff3c33c", + "url": "https://api.github.com/repos/ByteInternet/deployer/zipball/a3bab59d448de84099e52fc5b6c652737a8c2942", + "reference": "a3bab59d448de84099e52fc5b6c652737a8c2942", "shasum": "" }, "require": { @@ -26,8 +161,9 @@ "php": "^8.0|^7.3", "psr/http-message": "^1", "react/http": "^1.5", - "symfony/console": "^5", + "symfony/console": "^5.4.9", "symfony/polyfill-php80": "^1.22", + "symfony/polyfill-php81": "^1.26", "symfony/process": "^5", "symfony/yaml": "^5" }, @@ -39,21 +175,39 @@ "slevomat/coding-standard": "^7.0", "squizlabs/php_codesniffer": "^3.5" }, - "default-branch": true, "bin": [ "bin/dep" ], "type": "library", "autoload": { + "psr-4": { + "Deployer\\": "src/" + }, "files": [ "src/Support/helpers.php", "src/functions.php" + ] + }, + "scripts": { + "test": [ + "pest" ], - "psr-4": { - "Deployer\\": "src/" - } + "test:e2e": [ + "pest --config tests/e2e/phpunit-e2e.xml" + ], + "phpcs": [ + "phpcs" + ], + "fix": [ + "phpcbf" + ], + "phpstan": [ + "phpstan analyse -c phpstan.neon" + ], + "phpstan:baseline": [ + "@phpstan --generate-baseline tests/phpstan-baseline.neon" + ] }, - "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -67,44 +221,47 @@ "homepage": "https://deployer.org", "support": { "docs": "https://deployer.org/docs", - "issues": "https://github.com/deployphp/deployer/issues", - "source": "https://github.com/deployphp/deployer" + "source": "https://github.com/deployphp/deployer", + "issues": "https://github.com/deployphp/deployer/issues" }, "funding": [ { - "url": "https://github.com/sponsors/antonmedv", - "type": "github" + "type": "github", + "url": "https://github.com/sponsors/antonmedv" } ], - "time": "2022-08-04T06:42:59+00:00" + "time": "2024-04-17T21:19:58+00:00" }, { "name": "doctrine/annotations", - "version": "1.13.3", + "version": "1.14.4", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0" + "reference": "253dca476f70808a5aeed3a47cc2cc88c5cab915" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/253dca476f70808a5aeed3a47cc2cc88c5cab915", + "reference": "253dca476f70808a5aeed3a47cc2cc88c5cab915", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", + "doctrine/lexer": "^1 || ^2", "ext-tokenizer": "*", "php": "^7.1 || ^8.0", "psr/cache": "^1 || ^2 || ^3" }, "require-dev": { "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^1.4.10 || ^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2", - "vimeo/psalm": "^4.10" + "doctrine/coding-standard": "^9 || ^12", + "phpstan/phpstan": "~1.4.10 || ^1.10.28", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7", + "vimeo/psalm": "^4.30 || ^5.14" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" }, "type": "library", "autoload": { @@ -147,37 +304,88 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.13.3" + "source": "https://github.com/doctrine/annotations/tree/1.14.4" + }, + "abandoned": true, + "time": "2024-09-05T10:15:52+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" }, - "time": "2022-07-02T10:48:51+00:00" + "time": "2025-04-07T20:06:18+00:00" }, { "name": "doctrine/lexer", - "version": "1.2.3", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", + "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.0", "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9.0", + "doctrine/coding-standard": "^9 || ^12", "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.11" + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.21" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + "Doctrine\\Common\\Lexer\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -209,7 +417,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.3" + "source": "https://github.com/doctrine/lexer/tree/2.1.1" }, "funding": [ { @@ -225,32 +433,32 @@ "type": "tidelift" } ], - "time": "2022-02-28T11:07:21+00:00" + "time": "2024-02-05T11:35:39+00:00" }, { "name": "evenement/evenement", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/igorw/evenement.git", - "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7" + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7", - "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", "shasum": "" }, "require": { "php": ">=7.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9 || ^6" }, "type": "library", "autoload": { - "psr-0": { - "Evenement": "src" + "psr-4": { + "Evenement\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -270,9 +478,9 @@ ], "support": { "issues": "https://github.com/igorw/evenement/issues", - "source": "https://github.com/igorw/evenement/tree/master" + "source": "https://github.com/igorw/evenement/tree/v3.0.2" }, - "time": "2017-07-23T21:35:13+00:00" + "time": "2023-08-08T05:53:35+00:00" }, { "name": "fig/http-message-util", @@ -331,81 +539,162 @@ "time": "2020-11-24T22:02:12+00:00" }, { - "name": "hypernode/deploy-configuration", - "version": "v2.x-dev", + "name": "guzzlehttp/guzzle", + "version": "7.10.0", "source": { "type": "git", - "url": "https://github.com/ByteInternet/hypernode-deploy-configuration.git", - "reference": "5d3540f51f7cd9a76b8c28f21e8aefe1243b16dd" + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ByteInternet/hypernode-deploy-configuration/zipball/5d3540f51f7cd9a76b8c28f21e8aefe1243b16dd", - "reference": "5d3540f51f7cd9a76b8c28f21e8aefe1243b16dd", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { - "deployer/deployer": "^7.0", - "psr/log": "^1.0" + "ext-json": "*", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" }, - "conflict": { - "hipex/deploy-configuration": "*" + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" }, "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, "autoload": { "files": [ - "src/autoload.php", - "src/functions.php" + "src/functions_include.php" ], "psr-4": { - "Hypernode\\DeployConfiguration\\": "src/" + "GuzzleHttp\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "OSL-3.0" + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" ], - "description": "Hypernode deploy configuration files", "support": { - "issues": "https://github.com/ByteInternet/hypernode-deploy-configuration/issues", - "source": "https://github.com/ByteInternet/hypernode-deploy-configuration/tree/v2" + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, - "time": "2022-08-16T11:52:04+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2025-08-23T22:36:01+00:00" }, { - "name": "justinrainbow/json-schema", - "version": "5.2.12", + "name": "guzzlehttp/promises", + "version": "2.3.0", "source": { "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60" + "url": "https://github.com/guzzle/promises.git", + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", - "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", - "json-schema/json-schema-test-suite": "1.2.0", - "phpunit/phpunit": "^4.8.35" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, - "bin": [ - "bin/validate-json" - ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { "psr-4": { - "JsonSchema\\": "src/JsonSchema/" + "GuzzleHttp\\Promise\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -414,65 +703,92 @@ ], "authors": [ { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" }, { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" }, { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" }, { - "name": "Robert Schönthal", - "email": "seroscho@googlemail.com" + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], - "description": "A library to validate a json schema.", - "homepage": "https://github.com/justinrainbow/json-schema", + "description": "Guzzle promises library", "keywords": [ - "json", - "schema" + "promise" ], "support": { - "issues": "https://github.com/justinrainbow/json-schema/issues", - "source": "https://github.com/justinrainbow/json-schema/tree/5.2.12" + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, - "time": "2022-04-13T08:02:27+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:34:08+00:00" }, { - "name": "laravel/serializable-closure", - "version": "v1.2.0", + "name": "guzzlehttp/psr7", + "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/laravel/serializable-closure.git", - "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" + "url": "https://github.com/guzzle/psr7.git", + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", - "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { - "php": "^7.3|^8.0" + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" }, "require-dev": { - "pestphp/pest": "^1.18", - "phpstan/phpstan": "^0.12.98", - "symfony/var-dumper": "^5.3" + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.x-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { "psr-4": { - "Laravel\\SerializableClosure\\": "src/" + "GuzzleHttp\\Psr7\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -481,16 +797,283 @@ ], "authors": [ { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" }, { - "name": "Nuno Maduro", - "email": "nuno@laravel.com" - } - ], - "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", - "keywords": [ + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.8.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2025-08-23T21:21:41+00:00" + }, + { + "name": "hypernode/api-client", + "version": "0.5.0", + "source": { + "type": "git", + "url": "https://github.com/ByteInternet/hypernode-api-php.git", + "reference": "9ae1ef127fecac2212e7e7872f034c247c7e71fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ByteInternet/hypernode-api-php/zipball/9ae1ef127fecac2212e7e7872f034c247c7e71fd", + "reference": "9ae1ef127fecac2212e7e7872f034c247c7e71fd", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0", + "ext-curl": "*", + "ext-json": "*", + "nesbot/carbon": "^3.0", + "php-http/client-common": "^2.5", + "php-http/discovery": "^1.14", + "psr/http-client-implementation": "^1.0", + "symfony/polyfill-php80": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.9", + "guzzlehttp/guzzle": "^7.4", + "nikic/php-parser": "^4.14", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Hypernode\\Api\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Hypernode" + } + ], + "description": "Hypernode API Client for PHP", + "support": { + "issues": "https://github.com/ByteInternet/hypernode-api-php/issues", + "source": "https://github.com/ByteInternet/hypernode-api-php/tree/0.5.0" + }, + "time": "2025-06-02T09:05:59+00:00" + }, + { + "name": "hypernode/deploy-configuration", + "version": "3.6.1", + "source": { + "type": "git", + "url": "https://github.com/ByteInternet/hypernode-deploy-configuration.git", + "reference": "9925cffca3d028044f39b9d081a288fd07dd67aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ByteInternet/hypernode-deploy-configuration/zipball/9925cffca3d028044f39b9d081a288fd07dd67aa", + "reference": "9925cffca3d028044f39b9d081a288fd07dd67aa", + "shasum": "" + }, + "require": { + "deployer/deployer": "7.0 - 7.4", + "psr/log": "^1.0||^2.0||^3.0", + "symfony/polyfill-php81": "^1.33" + }, + "conflict": { + "hipex/deploy-configuration": "*" + }, + "type": "library", + "autoload": { + "files": [ + "src/autoload.php", + "src/functions.php" + ], + "psr-4": { + "Hypernode\\DeployConfiguration\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "description": "Hypernode deploy configuration files", + "support": { + "issues": "https://github.com/ByteInternet/hypernode-deploy-configuration/issues", + "source": "https://github.com/ByteInternet/hypernode-deploy-configuration/tree/3.6.1" + }, + "time": "2026-01-19T14:43:33+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "5.3.1", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "b5a44b6391a3bbb75c9f2b73e1ef03d6045e1e20" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/b5a44b6391a3bbb75c9f2b73e1ef03d6045e1e20", + "reference": "b5a44b6391a3bbb75c9f2b73e1ef03d6045e1e20", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.35" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/5.3.1" + }, + "time": "2025-12-12T08:56:22+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.0.8", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "7581a4407012f5f53365e11bafc520fd7f36bc9b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/7581a4407012f5f53365e11bafc520fd7f36bc9b", + "reference": "7581a4407012f5f53365e11bafc520fd7f36bc9b", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0|^11.0|^12.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ "closure", "laravel", "serializable" @@ -499,20 +1082,125 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2022-05-16T17:09:47+00:00" + "time": "2026-01-08T16:22:46+00:00" + }, + { + "name": "nesbot/carbon", + "version": "3.11.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "bdb375400dcd162624531666db4799b36b64e4a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/bdb375400dcd162624531666db4799b36b64e4a1", + "reference": "bdb375400dcd162624531666db4799b36b64e4a1", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "<100.0", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2025-12-02T21:04:28+00:00" }, { "name": "php-di/invoker", - "version": "2.3.3", + "version": "2.3.7", "source": { "type": "git", "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786" + "reference": "3c1ddfdef181431fbc4be83378f6d036d59e81e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786", - "reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/3c1ddfdef181431fbc4be83378f6d036d59e81e1", + "reference": "3c1ddfdef181431fbc4be83378f6d036d59e81e1", "shasum": "" }, "require": { @@ -522,7 +1210,7 @@ "require-dev": { "athletic/athletic": "~0.1.8", "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.0 || ^10 || ^11 || ^12" }, "type": "library", "autoload": { @@ -546,7 +1234,7 @@ ], "support": { "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.3" + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.7" }, "funding": [ { @@ -554,43 +1242,40 @@ "type": "github" } ], - "time": "2021-12-13T09:22:56+00:00" + "time": "2025-08-30T10:22:22+00:00" }, { "name": "php-di/php-di", - "version": "6.4.0", + "version": "7.1.1", "source": { "type": "git", "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" + "reference": "f88054cc052e40dbe7b383c8817c19442d480352" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", - "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/f88054cc052e40dbe7b383c8817c19442d480352", + "reference": "f88054cc052e40dbe7b383c8817c19442d480352", "shasum": "" }, "require": { - "laravel/serializable-closure": "^1.0", - "php": ">=7.4.0", + "laravel/serializable-closure": "^1.0 || ^2.0", + "php": ">=8.0", "php-di/invoker": "^2.0", - "php-di/phpdoc-reader": "^2.0.1", - "psr/container": "^1.0" + "psr/container": "^1.1 || ^2.0" }, "provide": { "psr/container-implementation": "^1.0" }, "require-dev": { - "doctrine/annotations": "~1.10", - "friendsofphp/php-cs-fixer": "^2.4", - "mnapoli/phpunit-easymock": "^1.2", - "ocramius/proxy-manager": "^2.11.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^9.5" + "friendsofphp/php-cs-fixer": "^3", + "friendsofphp/proxy-manager-lts": "^1", + "mnapoli/phpunit-easymock": "^1.3", + "phpunit/phpunit": "^9.6 || ^10 || ^11", + "vimeo/psalm": "^5|^6" }, "suggest": { - "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", - "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + "friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)" }, "type": "library", "autoload": { @@ -618,7 +1303,7 @@ ], "support": { "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" + "source": "https://github.com/PHP-DI/PHP-DI/tree/7.1.1" }, "funding": [ { @@ -630,66 +1315,499 @@ "type": "tidelift" } ], - "time": "2022-04-09T16:46:38+00:00" + "time": "2025-08-16T11:10:48+00:00" }, { - "name": "php-di/phpdoc-reader", - "version": "2.2.1", + "name": "php-http/client-common", + "version": "2.7.3", "source": { "type": "git", - "url": "https://github.com/PHP-DI/PhpDocReader.git", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + "url": "https://github.com/php-http/client-common.git", + "reference": "dcc6de29c90dd74faab55f71b79d89409c4bf0c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/client-common/zipball/dcc6de29c90dd74faab55f71b79d89409c4bf0c1", + "reference": "dcc6de29c90dd74faab55f71b79d89409c4bf0c1", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "php-http/httplug": "^2.0", + "php-http/message": "^1.6", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 || ^2.0", + "symfony/options-resolver": "~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/polyfill-php80": "^1.17" + }, + "require-dev": { + "doctrine/instantiator": "^1.1", + "guzzlehttp/psr7": "^1.4", + "nyholm/psr7": "^1.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.33 || ^9.6.7" + }, + "suggest": { + "ext-json": "To detect JSON responses with the ContentTypePlugin", + "ext-libxml": "To detect XML responses with the ContentTypePlugin", + "php-http/cache-plugin": "PSR-6 Cache plugin", + "php-http/logger-plugin": "PSR-3 Logger plugin", + "php-http/stopwatch-plugin": "Symfony Stopwatch plugin" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Client\\Common\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Common HTTP Client implementations and tools for HTTPlug", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "common", + "http", + "httplug" + ], + "support": { + "issues": "https://github.com/php-http/client-common/issues", + "source": "https://github.com/php-http/client-common/tree/2.7.3" + }, + "time": "2025-11-29T19:12:34+00:00" + }, + { + "name": "php-http/discovery", + "version": "1.20.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.20.0" + }, + "time": "2024-10-02T11:20:13+00:00" + }, + { + "name": "php-http/httplug", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/php-http/httplug.git", + "reference": "5cad731844891a4c282f3f3e1b582c46839d22f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/httplug/zipball/5cad731844891a4c282f3f3e1b582c46839d22f4", + "reference": "5cad731844891a4c282f3f3e1b582c46839d22f4", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "php-http/promise": "^1.1", + "psr/http-client": "^1.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "friends-of-phpspec/phpspec-code-coverage": "^4.1 || ^5.0 || ^6.0", + "phpspec/phpspec": "^5.1 || ^6.0 || ^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "HTTPlug, the HTTP client abstraction for PHP", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "http" + ], + "support": { + "issues": "https://github.com/php-http/httplug/issues", + "source": "https://github.com/php-http/httplug/tree/2.4.1" + }, + "time": "2024-09-23T11:39:58+00:00" + }, + { + "name": "php-http/message", + "version": "1.16.2", + "source": { + "type": "git", + "url": "https://github.com/php-http/message.git", + "reference": "06dd5e8562f84e641bf929bfe699ee0f5ce8080a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message/zipball/06dd5e8562f84e641bf929bfe699ee0f5ce8080a", + "reference": "06dd5e8562f84e641bf929bfe699ee0f5ce8080a", + "shasum": "" + }, + "require": { + "clue/stream-filter": "^1.5", + "php": "^7.2 || ^8.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.6", + "ext-zlib": "*", + "guzzlehttp/psr7": "^1.0 || ^2.0", + "laminas/laminas-diactoros": "^2.0 || ^3.0", + "php-http/message-factory": "^1.0.2", + "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1", + "slim/slim": "^3.0" + }, + "suggest": { + "ext-zlib": "Used with compressor/decompressor streams", + "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories", + "laminas/laminas-diactoros": "Used with Diactoros Factories", + "slim/slim": "Used with Slim Framework PSR-7 implementation" + }, + "type": "library", + "autoload": { + "files": [ + "src/filters.php" + ], + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTP Message related tools", + "homepage": "http://php-http.org", + "keywords": [ + "http", + "message", + "psr-7" + ], + "support": { + "issues": "https://github.com/php-http/message/issues", + "source": "https://github.com/php-http/message/tree/1.16.2" + }, + "time": "2024-10-02T11:34:13+00:00" + }, + { + "name": "php-http/promise", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/php-http/promise.git", + "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/promise/zipball/fc85b1fba37c169a69a07ef0d5a8075770cc1f83", + "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "friends-of-phpspec/phpspec-code-coverage": "^4.3.2 || ^6.3", + "phpspec/phpspec": "^5.1.2 || ^6.2 || ^7.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Promise used for asynchronous HTTP requests", + "homepage": "http://httplug.io", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/php-http/promise/issues", + "source": "https://github.com/php-http/promise/tree/1.3.1" + }, + "time": "2024-03-15T13:55:21+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", - "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": ">=7.2.0" - }, - "require-dev": { - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^8.5|^9.0" + "php": ">=7.4.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "psr-4": { - "PhpDocReader\\": "src/PhpDocReader" + "Psr\\Container\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "phpdoc", - "reflection" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], "support": { - "issues": "https://github.com/PHP-DI/PhpDocReader/issues", - "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "time": "2020-10-12T12:39:22+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { - "name": "psr/cache", - "version": "3.0.0", + "name": "psr/http-client", + "version": "1.0.3", "source": { "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -699,7 +1817,7 @@ }, "autoload": { "psr-4": { - "Psr\\Cache\\": "src/" + "Psr\\Http\\Client\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -712,38 +1830,46 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interface for caching libraries", + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", "keywords": [ - "cache", + "http", + "http-client", "psr", - "psr-6" + "psr-18" ], "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" + "source": "https://github.com/php-fig/http-client" }, - "time": "2021-02-03T23:26:27+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { - "name": "psr/container", - "version": "1.1.2", + "name": "psr/http-factory", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.4.0" + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "Psr\\Http\\Message\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -756,42 +1882,43 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" ], "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2021-11-05T16:50:12+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", - "version": "1.0.1", + "version": "1.1", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -820,9 +1947,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/master" + "source": "https://github.com/php-fig/http-message/tree/1.1" }, - "time": "2016-08-06T14:39:51+00:00" + "time": "2023-04-04T09:50:52+00:00" }, { "name": "psr/log", @@ -874,18 +2001,62 @@ }, "time": "2021-05-03T11:20:27+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "react/cache", - "version": "v1.1.1", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/reactphp/cache.git", - "reference": "4bf736a2cccec7298bdf745db77585966fc2ca7e" + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/cache/zipball/4bf736a2cccec7298bdf745db77585966fc2ca7e", - "reference": "4bf736a2cccec7298bdf745db77585966fc2ca7e", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", "shasum": "" }, "require": { @@ -893,7 +2064,7 @@ "react/promise": "^3.0 || ^2.0 || ^1.1" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" }, "type": "library", "autoload": { @@ -936,49 +2107,45 @@ ], "support": { "issues": "https://github.com/reactphp/cache/issues", - "source": "https://github.com/reactphp/cache/tree/v1.1.1" + "source": "https://github.com/reactphp/cache/tree/v1.2.0" }, "funding": [ { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2021-02-02T06:47:52+00:00" + "time": "2022-11-30T15:59:55+00:00" }, { "name": "react/dns", - "version": "v1.9.0", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/reactphp/dns.git", - "reference": "6d38296756fa644e6cb1bfe95eff0f9a4ed6edcb" + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/6d38296756fa644e6cb1bfe95eff0f9a4ed6edcb", - "reference": "6d38296756fa644e6cb1bfe95eff0f9a4ed6edcb", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", "shasum": "" }, "require": { "php": ">=5.3.0", "react/cache": "^1.0 || ^0.6 || ^0.5", "react/event-loop": "^1.2", - "react/promise": "^3.0 || ^2.7 || ^1.2.1", - "react/promise-timer": "^1.8" + "react/promise": "^3.2 || ^2.7 || ^1.2.1" }, "require-dev": { - "clue/block-react": "^1.2", - "phpunit/phpunit": "^9.3 || ^4.8.35" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { "psr-4": { - "React\\Dns\\": "src" + "React\\Dns\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1016,49 +2183,43 @@ ], "support": { "issues": "https://github.com/reactphp/dns/issues", - "source": "https://github.com/reactphp/dns/tree/v1.9.0" + "source": "https://github.com/reactphp/dns/tree/v1.14.0" }, "funding": [ { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2021-12-20T08:46:54+00:00" + "time": "2025-11-18T19:34:28+00:00" }, { "name": "react/event-loop", - "version": "v1.3.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/reactphp/event-loop.git", - "reference": "187fb56f46d424afb6ec4ad089269c72eec2e137" + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/187fb56f46d424afb6ec4ad089269c72eec2e137", - "reference": "187fb56f46d424afb6ec4ad089269c72eec2e137", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", "shasum": "" }, "require": { "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "suggest": { - "ext-event": "~1.0 for ExtEventLoop", - "ext-pcntl": "For signal handling support when using the StreamSelectLoop", - "ext-uv": "* for ExtUvLoop" + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" }, "type": "library", "autoload": { "psr-4": { - "React\\EventLoop\\": "src" + "React\\EventLoop\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1094,32 +2255,28 @@ ], "support": { "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.3.0" + "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" }, "funding": [ { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2022-03-17T11:10:22+00:00" + "time": "2025-11-17T20:46:25+00:00" }, { "name": "react/http", - "version": "v1.6.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/reactphp/http.git", - "reference": "59961cc4a5b14481728f07c591546be18fa3a5c7" + "reference": "8db02de41dcca82037367f67a2d4be365b1c4db9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/http/zipball/59961cc4a5b14481728f07c591546be18fa3a5c7", - "reference": "59961cc4a5b14481728f07c591546be18fa3a5c7", + "url": "https://api.github.com/repos/reactphp/http/zipball/8db02de41dcca82037367f67a2d4be365b1c4db9", + "reference": "8db02de41dcca82037367f67a2d4be365b1c4db9", "shasum": "" }, "require": { @@ -1128,23 +2285,23 @@ "php": ">=5.3.0", "psr/http-message": "^1.0", "react/event-loop": "^1.2", - "react/promise": "^2.3 || ^1.2.1", - "react/promise-stream": "^1.1", - "react/socket": "^1.9", - "react/stream": "^1.2", - "ringcentral/psr7": "^1.2" + "react/promise": "^3.2 || ^2.3 || ^1.2.1", + "react/socket": "^1.16", + "react/stream": "^1.4" }, "require-dev": { - "clue/block-react": "^1.5", - "clue/http-proxy-react": "^1.7", - "clue/reactphp-ssh-proxy": "^1.3", - "clue/socks-react": "^1.3", - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" + "clue/http-proxy-react": "^1.8", + "clue/reactphp-ssh-proxy": "^1.4", + "clue/socks-react": "^1.4", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.2 || ^3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { "psr-4": { - "React\\Http\\": "src" + "React\\Http\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1189,39 +2346,36 @@ ], "support": { "issues": "https://github.com/reactphp/http/issues", - "source": "https://github.com/reactphp/http/tree/v1.6.0" + "source": "https://github.com/reactphp/http/tree/v1.11.0" }, "funding": [ { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2022-02-03T13:17:37+00:00" + "time": "2024-11-20T15:24:08+00:00" }, { "name": "react/promise", - "version": "v2.9.0", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910" + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/234f8fd1023c9158e2314fa9d7d0e6a83db42910", - "reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": ">=7.1.0" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.36" + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", "autoload": { @@ -1259,227 +2413,54 @@ } ], "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "keywords": [ - "promise", - "promises" - ], - "support": { - "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v2.9.0" - }, - "funding": [ - { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" - } - ], - "time": "2022-02-11T10:27:51+00:00" - }, - { - "name": "react/promise-stream", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise-stream.git", - "reference": "ef05517b99e4363beaa7993d4e2d6c50f1b22a09" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise-stream/zipball/ef05517b99e4363beaa7993d4e2d6c50f1b22a09", - "reference": "ef05517b99e4363beaa7993d4e2d6c50f1b22a09", - "shasum": "" - }, - "require": { - "php": ">=5.3", - "react/promise": "^3 || ^2.1 || ^1.2", - "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.6" - }, - "require-dev": { - "clue/block-react": "^1.0", - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", - "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3", - "react/promise-timer": "^1.0" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "React\\Promise\\Stream\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "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": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "The missing link between Promise-land and Stream-land for ReactPHP", - "homepage": "https://github.com/reactphp/promise-stream", - "keywords": [ - "Buffer", - "async", - "promise", - "reactphp", - "stream", - "unwrap" - ], - "support": { - "issues": "https://github.com/reactphp/promise-stream/issues", - "source": "https://github.com/reactphp/promise-stream/tree/v1.4.0" - }, - "funding": [ - { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" - } - ], - "time": "2022-06-20T10:36:51+00:00" - }, - { - "name": "react/promise-timer", - "version": "v1.9.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise-timer.git", - "reference": "aa7a73c74b8d8c0f622f5982ff7b0351bc29e495" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/aa7a73c74b8d8c0f622f5982ff7b0351bc29e495", - "reference": "aa7a73c74b8d8c0f622f5982ff7b0351bc29e495", - "shasum": "" - }, - "require": { - "php": ">=5.3", - "react/event-loop": "^1.2", - "react/promise": "^3.0 || ^2.7.0 || ^1.2.1" - }, - "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "React\\Promise\\Timer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "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": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.", - "homepage": "https://github.com/reactphp/promise-timer", - "keywords": [ - "async", - "event-loop", + "keywords": [ "promise", - "reactphp", - "timeout", - "timer" + "promises" ], "support": { - "issues": "https://github.com/reactphp/promise-timer/issues", - "source": "https://github.com/reactphp/promise-timer/tree/v1.9.0" + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" }, "funding": [ { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2022-06-13T13:41:03+00:00" + "time": "2025-08-19T18:57:03+00:00" }, { "name": "react/socket", - "version": "v1.11.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/reactphp/socket.git", - "reference": "f474156aaab4f09041144fa8b57c7d70aed32a1c" + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/f474156aaab4f09041144fa8b57c7d70aed32a1c", - "reference": "f474156aaab4f09041144fa8b57c7d70aed32a1c", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", "shasum": "" }, "require": { "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "php": ">=5.3.0", - "react/dns": "^1.8", + "react/dns": "^1.13", "react/event-loop": "^1.2", - "react/promise": "^2.6.0 || ^1.2.1", - "react/promise-timer": "^1.8", - "react/stream": "^1.2" + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" }, "require-dev": { - "clue/block-react": "^1.5", - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", - "react/promise-stream": "^1.2" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { "psr-4": { - "React\\Socket\\": "src" + "React\\Socket\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1518,32 +2499,28 @@ ], "support": { "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.11.0" + "source": "https://github.com/reactphp/socket/tree/v1.17.0" }, "funding": [ { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2022-01-14T10:14:32+00:00" + "time": "2025-11-19T20:47:34+00:00" }, { "name": "react/stream", - "version": "v1.2.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/reactphp/stream.git", - "reference": "7a423506ee1903e89f1e08ec5f0ed430ff784ae9" + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/7a423506ee1903e89f1e08ec5f0ed430ff784ae9", - "reference": "7a423506ee1903e89f1e08ec5f0ed430ff784ae9", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", "shasum": "" }, "require": { @@ -1553,12 +2530,12 @@ }, "require-dev": { "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "type": "library", "autoload": { "psr-4": { - "React\\Stream\\": "src" + "React\\Stream\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1600,57 +2577,287 @@ ], "support": { "issues": "https://github.com/reactphp/stream/issues", - "source": "https://github.com/reactphp/stream/tree/v1.2.0" + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, + { + "name": "symfony/clock", + "version": "v6.4.30", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "fb2df4bc9e3037c4765ba7fd29e00167001a9b68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/fb2df4bc9e3037c4765ba7fd29e00167001a9b68", + "reference": "fb2df4bc9e3037c4765ba7fd29e00167001a9b68", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "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": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v6.4.30" }, "funding": [ { - "url": "https://github.com/WyriHaximus", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://github.com/clue", + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2021-07-11T12:37:55+00:00" + "time": "2025-11-11T21:24:34+00:00" }, { - "name": "ringcentral/psr7", - "version": "1.3.0", + "name": "symfony/console", + "version": "v5.4.47", "source": { "type": "git", - "url": "https://github.com/ringcentral/psr7.git", - "reference": "360faaec4b563958b673fb52bbe94e37f14bc686" + "url": "https://github.com/symfony/console.git", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ringcentral/psr7/zipball/360faaec4b563958b673fb52bbe94e37f14bc686", - "reference": "360faaec4b563958b673fb52bbe94e37f14bc686", + "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", "shasum": "" }, "require": { - "php": ">=5.3", - "psr/http-message": "~1.0" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" }, "provide": { - "psr/http-message-implementation": "1.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-06T11:30:55+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.6-dev" } }, "autoload": { "files": [ - "src/functions_include.php" - ], - "psr-4": { - "RingCentral\\Psr7\\": "src/" + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "63741784cd7b9967975eec610b256eed3ede022b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", + "reference": "63741784cd7b9967975eec610b256eed3ede022b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1658,76 +2865,83 @@ ], "authors": [ { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "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/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "description": "PSR-7 message implementation", - "keywords": [ - "http", - "message", - "stream", - "uri" - ], - "support": { - "source": "https://github.com/ringcentral/psr7/tree/master" - }, - "time": "2018-05-29T20:21:04+00:00" + "time": "2024-09-28T13:32:08+00:00" }, { - "name": "symfony/console", - "version": "v5.4.11", + "name": "symfony/http-client", + "version": "v5.4.49", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "535846c7ee6bc4dd027ca0d93220601456734b10" + "url": "https://github.com/symfony/http-client.git", + "reference": "d77d8e212cde7b5c4a64142bf431522f19487c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/535846c7ee6bc4dd027ca0d93220601456734b10", - "reference": "535846c7ee6bc4dd027ca0d93220601456734b10", + "url": "https://api.github.com/repos/symfony/http-client/zipball/d77d8e212cde7b5c4a64142bf431522f19487c28", + "reference": "d77d8e212cde7b5c4a64142bf431522f19487c28", "shasum": "" }, "require": { "php": ">=7.2.5", + "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", + "symfony/http-client-contracts": "^2.5.4", + "symfony/polyfill-php73": "^1.11", "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" - }, - "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" + "symfony/service-contracts": "^1.0|^2|^3" }, "provide": { - "psr/log-implementation": "1.0|2.0" + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "2.4" }, "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "php-http/message-factory": "^1.0", + "psr/http-client": "^1.0", "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4.13|^5.1.5|^6.0", "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "symfony/stopwatch": "^4.4|^5.0|^6.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Console\\": "" + "Symfony\\Component\\HttpClient\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -1739,24 +2953,21 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Eases the creation of beautiful and testable command line interfaces", + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", "homepage": "https://symfony.com", "keywords": [ - "cli", - "command line", - "console", - "terminal" + "http" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.11" + "source": "https://github.com/symfony/http-client/tree/v5.4.49" }, "funding": [ { @@ -1772,39 +2983,42 @@ "type": "tidelift" } ], - "time": "2022-07-22T10:42:43+00:00" + "time": "2024-11-28T08:37:04+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.1.1", + "name": "symfony/http-client-contracts", + "version": "v2.5.5", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918" + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "48ef1d0a082885877b664332b9427662065a360c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", - "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/48ef1d0a082885877b664332b9427662065a360c", + "reference": "48ef1d0a082885877b664332b9427662065a360c", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.2.5" + }, + "suggest": { + "symfony/http-client-implementation": "" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.1-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" } }, "autoload": { - "files": [ - "function.php" - ] + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1820,10 +3034,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "A generic function and convention to trigger deprecation notices", + "description": "Generic abstractions related to HTTP clients", "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1" + "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.5" }, "funding": [ { @@ -1839,31 +3061,30 @@ "type": "tidelift" } ], - "time": "2022-02-25T11:15:52+00:00" + "time": "2024-11-28T08:37:04+00:00" }, { - "name": "symfony/finder", - "version": "v5.4.11", + "name": "symfony/options-resolver", + "version": "v6.4.30", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" + "url": "https://github.com/symfony/options-resolver.git", + "reference": "eeaa8cabe54c7b3516938c72a4a161c0cc80a34f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", - "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/eeaa8cabe54c7b3516938c72a4a161c0cc80a34f", + "reference": "eeaa8cabe54c7b3516938c72a4a161c0cc80a34f", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Finder\\": "" + "Symfony\\Component\\OptionsResolver\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -1883,10 +3104,15 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Finds files and directories via an intuitive fluent interface", + "description": "Provides an improved replacement for the array_replace PHP function", "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.11" + "source": "https://github.com/symfony/options-resolver/tree/v6.4.30" }, "funding": [ { @@ -1897,64 +3123,134 @@ "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": "2022-07-29T07:37:50+00:00" + "time": "2025-11-12T13:06:53+00:00" }, { - "name": "symfony/http-client", - "version": "v5.4.11", + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/http-client.git", - "reference": "5c5c37eb2a276d8d7d669dd76688aa1606ee78fb" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/5c5c37eb2a276d8d7d669dd76688aa1606ee78fb", - "reference": "5c5c37eb2a276d8d7d669dd76688aa1606ee78fb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/http-client-contracts": "^2.4", - "symfony/polyfill-php73": "^1.11", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.0|^2|^3" + "php": ">=7.2" }, "provide": { - "php-http/async-client-implementation": "*", - "php-http/client-implementation": "*", - "psr/http-client-implementation": "1.0", - "symfony/http-client-implementation": "2.4" + "ext-ctype": "*" }, - "require-dev": { - "amphp/amp": "^2.5", - "amphp/http-client": "^4.2.1", - "amphp/http-tunnel": "^1.0", - "amphp/socket": "^1.1", - "guzzlehttp/promises": "^1.4", - "nyholm/psr7": "^1.0", - "php-http/httplug": "^1.0|^2.0", - "psr/http-client": "^1.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4.13|^5.1.5|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0" + "suggest": { + "ext-ctype": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\HttpClient\\": "" + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, - "exclude-from-classmap": [ - "/Tests/" - ] + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1970,10 +3266,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/http-client/tree/v5.4.11" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -1984,47 +3288,54 @@ "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": "2022-07-28T13:33:28+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { - "name": "symfony/http-client-contracts", - "version": "v2.5.2", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", - "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=7.2" }, "suggest": { - "symfony/http-client-implementation": "" + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Contracts\\HttpClient\\": "" - } + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2040,18 +3351,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to HTTP clients", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -2062,44 +3373,46 @@ "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": "2022-04-12T15:48:08+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.26.0", + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-iconv": "*", + "php": ">=7.2" }, "provide": { - "ext-ctype": "*" + "ext-mbstring": "*" }, "suggest": { - "ext-ctype": "For best performance" + "ext-mbstring": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2107,7 +3420,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" + "Symfony\\Polyfill\\Mbstring\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2116,24 +3429,25 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "ctype", + "mbstring", "polyfill", - "portable" + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -2144,41 +3458,39 @@ "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": "2022-05-24T11:49:31+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.26.0", + "name": "symfony/polyfill-php73", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "433d05519ce6990bf3530fba6957499d327395c2" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", - "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "shasum": "" }, "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2186,8 +3498,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2203,18 +3518,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "grapheme", - "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" }, "funding": [ { @@ -2225,41 +3538,39 @@ "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": "2022-05-24T11:49:31+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.26.0", + "name": "symfony/polyfill-php80", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2267,7 +3578,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + "Symfony\\Polyfill\\Php80\\": "" }, "classmap": [ "Resources/stubs" @@ -2278,6 +3589,10 @@ "MIT" ], "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -2287,18 +3602,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "intl", - "normalizer", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -2309,44 +3622,39 @@ "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": "2022-05-24T11:49:31+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.26.0", + "name": "symfony/polyfill-php81", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2354,8 +3662,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2371,17 +3682,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" }, "funding": [ { @@ -2392,38 +3702,39 @@ "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": "2022-05-24T11:49:31+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-php72", - "version": "v1.26.0", + "name": "symfony/polyfill-php83", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2", - "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2431,8 +3742,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2448,7 +3762,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -2457,7 +3771,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" }, "funding": [ { @@ -2468,49 +3782,42 @@ "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": "2022-05-24T11:49:31+00:00" + "time": "2025-07-08T02:45:35+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.26.0", + "name": "symfony/process", + "version": "v5.4.47", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" + "url": "https://github.com/symfony/process.git", + "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", + "url": "https://api.github.com/repos/symfony/process/zipball/5d1662fb32ebc94f17ddb8d635454a776066733d", + "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" + "Symfony\\Component\\Process\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2519,24 +3826,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" + "source": "https://github.com/symfony/process/tree/v5.4.47" }, "funding": [ { @@ -2552,44 +3853,46 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2024-11-06T11:36:42+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.26.0", + "name": "symfony/service-contracts", + "version": "v3.6.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" + "Symfony\\Contracts\\Service\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2597,10 +3900,6 @@ "MIT" ], "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -2610,16 +3909,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Generic abstractions related to writing services", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -2630,35 +3931,54 @@ "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": "2022-05-10T07:21:04+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { - "name": "symfony/process", - "version": "v5.4.11", + "name": "symfony/string", + "version": "v6.4.30", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1" + "url": "https://github.com/symfony/string.git", + "reference": "50590a057841fa6bf69d12eceffce3465b9e32cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1", - "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1", + "url": "https://api.github.com/repos/symfony/string/zipball/50590a057841fa6bf69d12eceffce3465b9e32cb", + "reference": "50590a057841fa6bf69d12eceffce3465b9e32cb", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { - "Symfony\\Component\\Process\\": "" + "Symfony\\Component\\String\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2670,18 +3990,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Executes commands in sub-processes", + "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/process/tree/v5.4.11" + "source": "https://github.com/symfony/string/tree/v6.4.30" }, "funding": [ { @@ -2692,52 +4020,76 @@ "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": "2022-06-27T16:58:25+00:00" + "time": "2025-11-21T18:03:05+00:00" }, { - "name": "symfony/service-contracts", - "version": "v2.5.2", + "name": "symfony/translation", + "version": "v6.4.31", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + "url": "https://github.com/symfony/translation.git", + "reference": "81579408ecf7dc5aa2d8462a6d5c3a430a80e6f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "url": "https://api.github.com/repos/symfony/translation/zipball/81579408ecf7dc5aa2d8462a6d5c3a430a80e6f2", + "reference": "81579408ecf7dc5aa2d8462a6d5c3a430a80e6f2", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" }, "conflict": { - "ext-psr": "<1.1|>=2" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.18|^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2745,26 +4097,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to writing services", + "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/translation/tree/v6.4.31" }, "funding": [ { @@ -2775,53 +4119,50 @@ "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": "2022-05-30T19:17:29+00:00" + "time": "2025-12-18T11:37:55+00:00" }, { - "name": "symfony/string", - "version": "v6.1.3", + "name": "symfony/translation-contracts", + "version": "v3.6.1", "source": { "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "f35241f45c30bcd9046af2bb200a7086f70e1d6b" + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f35241f45c30bcd9046af2bb200a7086f70e1d6b", - "reference": "f35241f45c30bcd9046af2bb200a7086f70e1d6b", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/translation-contracts": "<2.0" - }, - "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/translation-contracts": "^2.0|^3.0", - "symfony/var-exporter": "^5.4|^6.0" + "php": ">=8.1" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, "autoload": { - "files": [ - "Resources/functions.php" - ], "psr-4": { - "Symfony\\Component\\String\\": "" + "Symfony\\Contracts\\Translation\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2838,18 +4179,18 @@ "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", + "description": "Generic abstractions related to translation", "homepage": "https://symfony.com", "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.1.3" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" }, "funding": [ { @@ -2860,25 +4201,29 @@ "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": "2022-07-27T15:50:51+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/yaml", - "version": "v5.4.11", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "05d4ea560f3402c6c116afd99fdc66e60eda227e" + "reference": "a454d47278cc16a5db371fe73ae66a78a633371e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/05d4ea560f3402c6c116afd99fdc66e60eda227e", - "reference": "05d4ea560f3402c6c116afd99fdc66e60eda227e", + "url": "https://api.github.com/repos/symfony/yaml/zipball/a454d47278cc16a5db371fe73ae66a78a633371e", + "reference": "a454d47278cc16a5db371fe73ae66a78a633371e", "shasum": "" }, "require": { @@ -2924,7 +4269,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.11" + "source": "https://github.com/symfony/yaml/tree/v5.4.45" }, "funding": [ { @@ -2940,42 +4285,41 @@ "type": "tidelift" } ], - "time": "2022-06-27T16:58:25+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "twig/twig", - "version": "v2.15.2", + "version": "v3.22.2", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "3e43405a9a8b578809426339cc3780e16fba0c52" + "reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/3e43405a9a8b578809426339cc3780e16fba0c52", - "reference": "3e43405a9a8b578809426339cc3780e16fba0c52", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/946ddeafa3c9f4ce279d1f34051af041db0e16f2", + "reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2", "shasum": "" }, "require": { - "php": ">=7.1.3", + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php72": "^1.8" + "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { - "psr/container": "^1.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "phpstan/phpstan": "^2.0", + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.15-dev" - } - }, "autoload": { - "psr-0": { - "Twig_": "lib/" - }, + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], "psr-4": { "Twig\\": "src/" } @@ -3008,7 +4352,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v2.15.2" + "source": "https://github.com/twigphp/Twig/tree/v3.22.2" }, "funding": [ { @@ -3020,32 +4364,32 @@ "type": "tidelift" } ], - "time": "2022-08-12T06:43:37+00:00" + "time": "2025-12-14T11:28:47+00:00" }, { "name": "webmozart/assert", - "version": "1.11.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", "shasum": "" }, "require": { "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", "php": "^7.2 || ^8.0" }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", "extra": { @@ -3076,51 +4420,44 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" + "source": "https://github.com/webmozarts/assert/tree/1.12.1" }, - "time": "2022-06-03T18:03:27+00:00" + "time": "2025-10-29T15:56:20+00:00" } ], "packages-dev": [ { "name": "amphp/amp", - "version": "v2.6.2", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" + "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "url": "https://api.github.com/repos/amphp/amp/zipball/fa0ab33a6f47a82929c38d03ca47ebb71086a93f", + "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" }, "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1", - "ext-json": "*", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^7 | ^8 | ^9", - "psalm/phar": "^3.11@dev", - "react/promise": "^2" + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, "autoload": { "files": [ - "lib/functions.php", - "lib/Internal/functions.php" + "src/functions.php", + "src/Future/functions.php", + "src/Internal/functions.php" ], "psr-4": { - "Amp\\": "lib" + "Amp\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3128,10 +4465,6 @@ "MIT" ], "authors": [ - { - "name": "Daniel Lowrey", - "email": "rdlowrey@php.net" - }, { "name": "Aaron Piotrowski", "email": "aaron@trowski.com" @@ -3143,6 +4476,10 @@ { "name": "Niklas Keller", "email": "me@kelunik.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" } ], "description": "A non-blocking concurrency framework for PHP applications.", @@ -3159,9 +4496,8 @@ "promise" ], "support": { - "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.2" + "source": "https://github.com/amphp/amp/tree/v3.1.1" }, "funding": [ { @@ -3169,46 +4505,45 @@ "type": "github" } ], - "time": "2022-02-20T17:52:18+00:00" + "time": "2025-08-27T21:42:00+00:00" }, { "name": "amphp/byte-stream", - "version": "v1.8.1", + "version": "v2.1.2", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46", "shasum": "" }, "require": { - "amphp/amp": "^2", - "php": ">=7.1" + "amphp/amp": "^3", + "amphp/parser": "^1.1", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2.3" }, "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1.4", - "friendsofphp/php-cs-fixer": "^2.3", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^6 || ^7 || ^8", - "psalm/phar": "^3.11.4" + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.22.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "files": [ - "lib/functions.php" + "src/functions.php", + "src/Internal/functions.php" ], "psr-4": { - "Amp\\ByteStream\\": "lib" + "Amp\\ByteStream\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3226,7 +4561,7 @@ } ], "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "http://amphp.org/byte-stream", + "homepage": "https://amphp.org/byte-stream", "keywords": [ "amp", "amphp", @@ -3236,9 +4571,8 @@ "stream" ], "support": { - "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + "source": "https://github.com/amphp/byte-stream/tree/v2.1.2" }, "funding": [ { @@ -3246,44 +4580,229 @@ "type": "github" } ], - "time": "2021-03-30T17:13:30+00:00" + "time": "2025-03-16T17:10:27+00:00" }, { - "name": "composer/package-versions-deprecated", - "version": "1.11.99.5", + "name": "amphp/parser", + "version": "v1.1.1", "source": { "type": "git", - "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" + "url": "https://github.com/amphp/parser.git", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", + "url": "https://api.github.com/repos/amphp/parser/zipball/3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7", "shasum": "" }, "require": { - "composer-plugin-api": "^1.1.0 || ^2.0", - "php": "^7 || ^8" + "php": ">=7.4" }, - "replace": { - "ocramius/package-versions": "1.11.99" + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Parser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A generator parser to make streaming parsers simple.", + "homepage": "https://github.com/amphp/parser", + "keywords": [ + "async", + "non-blocking", + "parser", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/parser/issues", + "source": "https://github.com/amphp/parser/tree/v1.1.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-03-21T19:16:53+00:00" + }, + { + "name": "amphp/pipeline", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/pipeline.git", + "reference": "7b52598c2e9105ebcddf247fc523161581930367" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/7b52598c2e9105ebcddf247fc523161581930367", + "reference": "7b52598c2e9105ebcddf247fc523161581930367", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "php": ">=8.1", + "revolt/event-loop": "^1" }, "require-dev": { - "composer/composer": "^1.9.3 || ^2.0@dev", - "ext-zip": "^1.13", - "phpunit/phpunit": "^6.5 || ^7" + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.18" }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "1.x-dev" + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Pipeline\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Asynchronous iterators and operators.", + "homepage": "https://amphp.org/pipeline", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "iterator", + "non-blocking" + ], + "support": { + "issues": "https://github.com/amphp/pipeline/issues", + "source": "https://github.com/amphp/pipeline/tree/v1.2.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-03-16T16:33:53+00:00" + }, + { + "name": "amphp/serialization", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/serialization.git", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "phpunit/phpunit": "^9 || ^8 || ^7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Serialization\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" } + ], + "description": "Serialization tools for IPC and data storage in PHP.", + "homepage": "https://github.com/amphp/serialization", + "keywords": [ + "async", + "asynchronous", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/amphp/serialization/issues", + "source": "https://github.com/amphp/serialization/tree/master" + }, + "time": "2020-03-25T21:39:07+00:00" + }, + { + "name": "amphp/sync", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/sync.git", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/sync/zipball/217097b785130d77cfcc58ff583cf26cd1770bf1", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1", + "shasum": "" }, + "require": { + "amphp/amp": "^3", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23" + }, + "type": "library", "autoload": { + "files": [ + "src/functions.php" + ], "psr-4": { - "PackageVersions\\": "src/PackageVersions" + "Amp\\Sync\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3292,59 +4811,71 @@ ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" }, { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" } ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "description": "Non-blocking synchronization primitives for PHP based on Amp and Revolt.", + "homepage": "https://github.com/amphp/sync", + "keywords": [ + "async", + "asynchronous", + "mutex", + "semaphore", + "synchronization" + ], "support": { - "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" + "issues": "https://github.com/amphp/sync/issues", + "source": "https://github.com/amphp/sync/tree/v2.3.0" }, "funding": [ { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", + "url": "https://github.com/amphp", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2022-01-17T14:14:24+00:00" + "time": "2024-08-03T19:31:26+00:00" }, { "name": "composer/pcre", - "version": "3.0.0", + "version": "3.3.2", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd" + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/e300eb6c535192decd27a85bc72a9290f0d6b3bd", - "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, "require-dev": { - "phpstan/phpstan": "^1.3", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, "branch-alias": { "dev-main": "3.x-dev" } @@ -3374,7 +4905,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.0.0" + "source": "https://github.com/composer/pcre/tree/3.3.2" }, "funding": [ { @@ -3390,28 +4921,28 @@ "type": "tidelift" } ], - "time": "2022-02-25T20:21:48+00:00" + "time": "2024-11-12T16:29:46+00:00" }, { "name": "composer/semver", - "version": "3.3.2", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { @@ -3453,9 +4984,9 @@ "versioning" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.2" + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -3465,26 +4996,22 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2022-04-01T19:23:25+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "composer/xdebug-handler", - "version": "3.0.3", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "ced299686f41dce890debac69273b47ffe98a40c" + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", - "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { @@ -3495,7 +5022,7 @@ "require-dev": { "phpstan/phpstan": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^6.0" + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { @@ -3519,9 +5046,9 @@ "performance" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" }, "funding": [ { @@ -3537,7 +5064,7 @@ "type": "tidelift" } ], - "time": "2022-02-25T21:32:43+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { "name": "dnoegel/php-xdg-base-dir", @@ -3578,30 +5105,30 @@ }, { "name": "doctrine/instantiator", - "version": "1.4.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -3628,7 +5155,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -3644,7 +5171,7 @@ "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "felixfbecker/advanced-json-rpc", @@ -3693,16 +5220,16 @@ }, { "name": "felixfbecker/language-server-protocol", - "version": "v1.5.2", + "version": "v1.5.3", "source": { "type": "git", "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9", "shasum": "" }, "require": { @@ -3743,22 +5270,83 @@ ], "support": { "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.3" + }, + "time": "2024-04-30T00:40:11+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, - "time": "2022-03-02T22:36:06+00:00" + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -3766,11 +5354,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "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", @@ -3796,7 +5385,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -3804,20 +5393,20 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "netresearch/jsonmapper", - "version": "v4.0.0", + "version": "v4.5.0", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d" + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", - "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8e76efb98ee8b6afc54687045e1b8dba55ac76e5", + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5", "shasum": "" }, "require": { @@ -3828,7 +5417,7 @@ "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0", + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", "squizlabs/php_codesniffer": "~3.5" }, "type": "library", @@ -3853,31 +5442,33 @@ "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0" + "source": "https://github.com/cweiske/jsonmapper/tree/v4.5.0" }, - "time": "2020-12-01T19:48:11+00:00" + "time": "2024-09-08T10:13:13+00:00" }, { "name": "nikic/php-parser", - "version": "v4.14.0", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -3885,7 +5476,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -3909,79 +5500,27 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" - }, - "time": "2022-05-31T20:59:12+00:00" - }, - { - "name": "openlss/lib-array2xml", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/nullivex/lib-array2xml.git", - "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", - "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "type": "library", - "autoload": { - "psr-0": { - "LSS": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Bryan Tong", - "email": "bryan@nullivex.com", - "homepage": "https://www.nullivex.com" - }, - { - "name": "Tony Butler", - "email": "spudz76@gmail.com", - "homepage": "https://www.nullivex.com" - } - ], - "description": "Array2XML conversion library credit to lalit.org", - "homepage": "https://www.nullivex.com", - "keywords": [ - "array", - "array conversion", - "xml", - "xml conversion" - ], - "support": { - "issues": "https://github.com/nullivex/lib-array2xml/issues", - "source": "https://github.com/nullivex/lib-array2xml/tree/master" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2019-03-29T20:06:56+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -4022,9 +5561,15 @@ "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.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -4132,28 +5677,35 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", + "version": "5.6.6", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.1", "ext-filter": "*", - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1 || ^2" }, "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" }, "type": "library", "extra": { @@ -4177,37 +5729,45 @@ }, { "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "email": "opensource@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.6" }, - "time": "2021-10-19T17:43:47+00:00" + "time": "2025-12-22T21:13:58+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.6.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "77a32518733312af16a44300404e945338981de3" + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", - "reference": "77a32518733312af16a44300404e945338981de3", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { "ext-tokenizer": "*", - "psalm/phar": "^4.8" + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" }, "type": "library", "extra": { @@ -4233,28 +5793,28 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" }, - "time": "2022-03-15T21:29:03+00:00" + "time": "2025-11-21T15:09:14+00:00" }, { "name": "phpro/grumphp-shim", - "version": "v1.13.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/phpro/grumphp-shim.git", - "reference": "973a933d176be41f1196d8db7851e32f985dd798" + "reference": "98f7d27631785b48270af88e33abcc591ae022fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpro/grumphp-shim/zipball/973a933d176be41f1196d8db7851e32f985dd798", - "reference": "973a933d176be41f1196d8db7851e32f985dd798", + "url": "https://api.github.com/repos/phpro/grumphp-shim/zipball/98f7d27631785b48270af88e33abcc591ae022fc", + "reference": "98f7d27631785b48270af88e33abcc591ae022fc", "shasum": "" }, "require": { "composer-plugin-api": "~2.0", "ext-json": "*", - "php": "^7.4 || ^8.0" + "php": "^8.0" }, "replace": { "phpro/grumphp": "self.version" @@ -4292,113 +5852,97 @@ "description": "GrumPHP Phar distribution", "support": { "issues": "https://github.com/phpro/grumphp-shim/issues", - "source": "https://github.com/phpro/grumphp-shim/tree/v1.13.0" + "source": "https://github.com/phpro/grumphp-shim/tree/v1.16.0" }, - "time": "2022-06-24T08:34:50+00:00" + "time": "2023-04-27T11:06:59+00:00" }, { - "name": "phpspec/prophecy", - "version": "v1.15.0", + "name": "phpstan/phpdoc-parser", + "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "16dbf9937da8d4528ceb2145c9c7c0bd29e26374" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/16dbf9937da8d4528ceb2145c9c7c0bd29e26374", + "reference": "16dbf9937da8d4528ceb2145c9c7c0bd29e26374", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.2", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" + "php": "^7.4 || ^8.0" }, "require-dev": { - "phpspec/phpspec": "^6.0 || ^7.0", - "phpunit/phpunit": "^8.0 || ^9.0" + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "psr-4": { - "Prophecy\\": "src/Prophecy" + "PHPStan\\PhpDocParser\\": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.1" }, - "time": "2021-12-08T12:19:24+00:00" + "time": "2026-01-12T11:33:04+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "7.0.15", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "819f92bba8b001d4363065928088de22f25a3a48" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/819f92bba8b001d4363065928088de22f25a3a48", - "reference": "819f92bba8b001d4363065928088de22f25a3a48", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-xmlwriter": "*", - "php": ">=7.2", - "phpunit/php-file-iterator": "^2.0.2", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.1.3 || ^4.0", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^4.2.2", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1.3" + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^8.2.2" + "phpunit/phpunit": "^9.6" }, "suggest": { - "ext-xdebug": "^2.7.2" + "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-master": "7.0-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -4426,7 +5970,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.15" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -4434,32 +5979,32 @@ "type": "github" } ], - "time": "2021-07-26T12:20:09+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "2.0.5", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -4486,7 +6031,70 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" }, "funding": [ { @@ -4494,26 +6102,34 @@ "type": "github" } ], - "time": "2021-12-02T12:42:26+00:00" + "time": "2020-09-28T05:58:55+00:00" }, { "name": "phpunit/php-text-template", - "version": "1.2.1", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -4537,37 +6153,135 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" }, - "time": "2015-06-21T13:50:34+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" }, { "name": "phpunit/php-timer", - "version": "2.1.3", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.31", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "945d0b7f346a084ce5549e95289962972c4272e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/945d0b7f346a084ce5549e95289962972c4272e5", + "reference": "945d0b7f346a084ce5549e95289962972c4272e5", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.5.0 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.32", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", + "sebastian/comparator": "^4.0.9", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.8", + "sebastian/global-state": "^5.0.8", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", + "sebastian/version": "^3.0.2" }, - "require-dev": { - "phpunit/phpunit": "^8.5" + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, + "bin": [ + "phpunit" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "9.6-dev" } }, "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], "classmap": [ "src/" ] @@ -4583,136 +6297,138 @@ "role": "lead" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", "keywords": [ - "timer" + "phpunit", + "testing", + "xunit" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3" + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.31" }, "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, { "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/phpunit", + "type": "tidelift" } ], - "time": "2020-11-30T08:20:02+00:00" + "time": "2025-12-06T07:45:52+00:00" }, { - "name": "phpunit/php-token-stream", - "version": "4.0.4", + "name": "revolt/event-loop", + "version": "v1.0.8", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3" + "url": "https://github.com/revoltphp/event-loop.git", + "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3", - "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/b6fc06dce8e9b523c9946138fa5e62181934f91c", + "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": "^7.3 || ^8.0" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.15" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "1.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Revolt\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "ceesjank@gmail.com" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" } ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "description": "Rock-solid event loop for concurrent PHP applications.", "keywords": [ - "tokenizer" + "async", + "asynchronous", + "concurrency", + "event", + "event-loop", + "non-blocking", + "scheduler" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", - "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master" + "issues": "https://github.com/revoltphp/event-loop/issues", + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.8" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "abandoned": true, - "time": "2020-08-04T08:28:15+00:00" + "time": "2025-08-27T21:33:23+00:00" }, { - "name": "phpunit/phpunit", - "version": "8.5.28", + "name": "sebastian/cli-parser", + "version": "1.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "8f2d1c9c7b30382459c871467853da1a6e44fbd4" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8f2d1c9c7b30382459c871467853da1a6e44fbd4", - "reference": "8f2d1c9c7b30382459c871467853da1a6e44fbd4", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.0", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.2", - "phpspec/prophecy": "^1.10.3", - "phpunit/php-code-coverage": "^7.0.12", - "phpunit/php-file-iterator": "^2.0.4", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1.2", - "sebastian/comparator": "^3.0.2", - "sebastian/diff": "^3.0.2", - "sebastian/environment": "^4.2.3", - "sebastian/exporter": "^3.1.2", - "sebastian/global-state": "^3.0.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0.1", - "sebastian/type": "^1.1.3", - "sebastian/version": "^2.0.1" + "php": ">=7.3" }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0.0" + "require-dev": { + "phpunit/phpunit": "^9.3" }, - "bin": [ - "phpunit" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "8.5-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -4731,568 +6447,100 @@ "role": "lead" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.28" + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" }, "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2022-07-29T09:20:50+00:00" + "time": "2024-03-02T06:27:43+00:00" }, { - "name": "roave/security-advisories", - "version": "dev-master", + "name": "sebastian/code-unit", + "version": "1.0.8", "source": { "type": "git", - "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "773292d413a97c357a0b49635afd5fdb1d4f314a" + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/773292d413a97c357a0b49635afd5fdb1d4f314a", - "reference": "773292d413a97c357a0b49635afd5fdb1d4f314a", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", "shasum": "" }, - "conflict": { - "3f/pygmentize": "<1.2", - "admidio/admidio": "<4.1.9", - "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", - "akaunting/akaunting": "<2.1.13", - "alextselegidis/easyappointments": "<=1.4.3", - "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", - "amazing/media2click": ">=1,<1.3.3", - "amphp/artax": "<1.0.6|>=2,<2.0.6", - "amphp/http": "<1.0.1", - "amphp/http-client": ">=4,<4.4", - "anchorcms/anchor-cms": "<=0.12.7", - "andreapollastri/cipi": "<=3.1.15", - "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6", - "appwrite/server-ce": "<0.11.1|>=0.12,<0.12.2", - "area17/twill": "<1.2.5|>=2,<2.5.3", - "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99", - "aws/aws-sdk-php": ">=3,<3.2.1", - "bagisto/bagisto": "<0.1.5", - "barrelstrength/sprout-base-email": "<1.2.7", - "barrelstrength/sprout-forms": "<3.9", - "barryvdh/laravel-translation-manager": "<0.6.2", - "baserproject/basercms": "<4.5.4", - "billz/raspap-webgui": "<=2.6.6", - "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", - "bmarshall511/wordpress_zero_spam": "<5.2.13", - "bolt/bolt": "<3.7.2", - "bolt/core": "<=4.2", - "bottelet/flarepoint": "<2.2.1", - "brightlocal/phpwhois": "<=4.2.5", - "brotkrueml/codehighlight": "<2.7", - "brotkrueml/schema": "<1.13.1|>=2,<2.5.1", - "brotkrueml/typo3-matomo-integration": "<1.3.2", - "buddypress/buddypress": "<7.2.1", - "bugsnag/bugsnag-laravel": ">=2,<2.0.2", - "bytefury/crater": "<6.0.2", - "cachethq/cachet": "<2.5.1", - "cakephp/cakephp": "<3.10.3|>=4,<4.0.6", - "cardgate/magento2": "<2.0.33", - "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", - "cartalyst/sentry": "<=2.1.6", - "catfan/medoo": "<1.7.5", - "centreon/centreon": "<20.10.7", - "cesnet/simplesamlphp-module-proxystatistics": "<3.1", - "codeception/codeception": "<3.1.3|>=4,<4.1.22", - "codeigniter/framework": "<=3.0.6", - "codeigniter4/framework": "<4.1.9", - "codiad/codiad": "<=2.8.4", - "composer/composer": "<1.10.26|>=2-alpha.1,<2.2.12|>=2.3,<2.3.5", - "concrete5/concrete5": "<9", - "concrete5/core": "<8.5.8|>=9,<9.1", - "contao-components/mediaelement": ">=2.14.2,<2.21.1", - "contao/contao": ">=4,<4.4.56|>=4.5,<4.9.18|>=4.10,<4.11.7|>=4.13,<4.13.3", - "contao/core": ">=2,<3.5.39", - "contao/core-bundle": "<4.9.18|>=4.10,<4.11.7|>=4.13,<4.13.3|= 4.10.0", - "contao/listing-bundle": ">=4,<4.4.8", - "contao/managed-edition": "<=1.5", - "craftcms/cms": "<3.7.36", - "croogo/croogo": "<3.0.7", - "cuyz/valinor": "<0.12", - "czproject/git-php": "<4.0.3", - "darylldoyle/safe-svg": "<1.9.10", - "datadog/dd-trace": ">=0.30,<0.30.2", - "david-garcia/phpwhois": "<=4.3.1", - "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1", - "directmailteam/direct-mail": "<5.2.4", - "doctrine/annotations": ">=1,<1.2.7", - "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", - "doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1", - "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2|>=3,<3.1.4", - "doctrine/doctrine-bundle": "<1.5.2", - "doctrine/doctrine-module": "<=0.7.1", - "doctrine/mongodb-odm": ">=1,<1.0.2", - "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", - "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", - "dolibarr/dolibarr": "<16|= 12.0.5|>= 3.3.beta1, < 13.0.2", - "dompdf/dompdf": "<2", - "drupal/core": ">=7,<7.91|>=8,<9.3.19|>=9.4,<9.4.3", - "drupal/drupal": ">=7,<7.80|>=8,<8.9.16|>=9,<9.1.12|>=9.2,<9.2.4", - "dweeves/magmi": "<=0.7.24", - "ecodev/newsletter": "<=4", - "ectouch/ectouch": "<=2.7.2", - "elefant/cms": "<1.3.13", - "elgg/elgg": "<3.3.24|>=4,<4.0.5", - "endroid/qr-code-bundle": "<3.4.2", - "enshrined/svg-sanitize": "<0.15", - "erusev/parsedown": "<1.7.2", - "ether/logs": "<3.0.4", - "ezsystems/demobundle": ">=5.4,<5.4.6.1", - "ezsystems/ez-support-tools": ">=2.2,<2.2.3", - "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1", - "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1|>=5.4,<5.4.11.1|>=2017.12,<2017.12.0.1", - "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", - "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.27", - "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1", - "ezsystems/ezplatform-kernel": "<=1.2.5|>=1.3,<1.3.19", - "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", - "ezsystems/ezplatform-richtext": ">=2.3,<=2.3.7", - "ezsystems/ezplatform-user": ">=1,<1.0.1", - "ezsystems/ezpublish-kernel": "<=6.13.8.1|>=7,<7.5.29", - "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.3.5.1", - "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", - "ezsystems/repository-forms": ">=2.3,<2.3.2.1", - "ezyang/htmlpurifier": "<4.1.1", - "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", - "facturascripts/facturascripts": "<=2022.8", - "feehi/cms": "<=2.1.1", - "feehi/feehicms": "<=0.1.3", - "fenom/fenom": "<=2.12.1", - "filegator/filegator": "<7.8", - "firebase/php-jwt": "<2", - "flarum/core": ">=1,<=1.0.1", - "flarum/sticky": ">=0.1-beta.14,<=0.1-beta.15", - "flarum/tags": "<=0.1-beta.13", - "fluidtypo3/vhs": "<5.1.1", - "fof/byobu": ">=0.3-beta.2,<1.1.7", - "fof/upload": "<1.2.3", - "fooman/tcpdf": "<6.2.22", - "forkcms/forkcms": "<5.11.1", - "fossar/tcpdf-parser": "<6.2.22", - "francoisjacquet/rosariosis": "<9.1", - "friendsofsymfony/oauth2-php": "<1.3", - "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", - "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", - "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", - "froala/wysiwyg-editor": "<3.2.7", - "froxlor/froxlor": "<=0.10.22", - "fuel/core": "<1.8.1", - "gaoming13/wechat-php-sdk": "<=1.10.2", - "genix/cms": "<=1.1.11", - "getgrav/grav": "<1.7.34", - "getkirby/cms": "<3.5.8", - "getkirby/panel": "<2.5.14", - "gilacms/gila": "<=1.11.4", - "globalpayments/php-sdk": "<2", - "google/protobuf": "<3.15", - "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", - "gree/jose": "<=2.2", - "gregwar/rst": "<1.0.3", - "grumpydictator/firefly-iii": "<5.6.5", - "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", - "guzzlehttp/psr7": "<1.8.4|>=2,<2.1.1", - "helloxz/imgurl": "= 2.31|<=2.31", - "hillelcoren/invoice-ninja": "<5.3.35", - "hjue/justwriting": "<=1", - "hov/jobfair": "<1.0.13|>=2,<2.0.2", - "hyn/multi-tenant": ">=5.6,<5.7.2", - "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4", - "ibexa/post-install": "<=1.0.4", - "icecoder/icecoder": "<=8.1", - "idno/known": "<=1.3.1", - "illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10", - "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.99999|>=4.2,<=4.2.99999|>=5,<=5.0.99999|>=5.1,<=5.1.99999|>=5.2,<=5.2.99999|>=5.3,<=5.3.99999|>=5.4,<=5.4.99999|>=5.5,<=5.5.49|>=5.6,<=5.6.99999|>=5.7,<=5.7.99999|>=5.8,<=5.8.99999|>=6,<6.18.31|>=7,<7.22.4", - "illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40", - "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", - "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", - "impresscms/impresscms": "<=1.4.3", - "in2code/femanager": "<5.5.1|>=6,<6.3.1", - "in2code/lux": "<17.6.1|>=18,<24.0.2", - "intelliants/subrion": "<=4.2.1", - "islandora/islandora": ">=2,<2.4.1", - "ivankristianto/phpwhois": "<=4.3", - "jackalope/jackalope-doctrine-dbal": "<1.7.4", - "james-heinrich/getid3": "<1.9.21", - "joomla/archive": "<1.1.12|>=2,<2.0.1", - "joomla/filesystem": "<1.6.2|>=2,<2.0.1", - "joomla/filter": "<1.4.4|>=2,<2.0.1", - "joomla/input": ">=2,<2.0.2", - "joomla/session": "<1.3.1", - "jsdecena/laracom": "<2.0.9", - "jsmitty12/phpwhois": "<5.1", - "kazist/phpwhois": "<=4.2.6", - "kevinpapst/kimai2": "<1.16.7", - "kitodo/presentation": "<3.1.2", - "klaviyo/magento2-extension": ">=1,<3", - "krayin/laravel-crm": "<1.2.2", - "kreait/firebase-php": ">=3.2,<3.8.1", - "la-haute-societe/tcpdf": "<6.2.22", - "laminas/laminas-diactoros": "<2.11.1", - "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", - "laminas/laminas-http": "<2.14.2", - "laravel/fortify": "<1.11.1", - "laravel/framework": "<6.20.42|>=7,<7.30.6|>=8,<8.75", - "laravel/laravel": "<=9.1.8", - "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", - "latte/latte": "<2.10.8", - "lavalite/cms": "<=5.8", - "lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5", - "league/commonmark": "<0.18.3", - "league/flysystem": "<1.1.4|>=2,<2.1.1", - "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", - "librenms/librenms": "<22.4", - "limesurvey/limesurvey": "<3.27.19", - "livehelperchat/livehelperchat": "<=3.91", - "livewire/livewire": ">2.2.4,<2.2.6", - "lms/routes": "<2.1.1", - "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", - "luyadev/yii-helpers": "<1.2.1", - "magento/community-edition": ">=2,<2.2.10|>=2.3,<2.3.3", - "magento/magento1ce": "<1.9.4.3", - "magento/magento1ee": ">=1,<1.14.4.3", - "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2-p.2", - "marcwillmann/turn": "<0.3.3", - "matyhtf/framework": "<3.0.6", - "mautic/core": "<4.3|= 2.13.1", - "mediawiki/core": ">=1.27,<1.27.6|>=1.29,<1.29.3|>=1.30,<1.30.2|>=1.31,<1.31.9|>=1.32,<1.32.6|>=1.32.99,<1.33.3|>=1.33.99,<1.34.3|>=1.34.99,<1.35", - "mezzio/mezzio-swoole": "<3.7|>=4,<4.3", - "microweber/microweber": "<1.3.1", - "miniorange/miniorange-saml": "<1.4.3", - "mittwald/typo3_forum": "<1.2.1", - "modx/revolution": "<= 2.8.3-pl|<2.8", - "mojo42/jirafeau": "<4.4", - "monolog/monolog": ">=1.8,<1.12", - "moodle/moodle": "<4.0.1", - "mustache/mustache": ">=2,<2.14.1", - "namshi/jose": "<2.2", - "neoan3-apps/template": "<1.1.1", - "neorazorx/facturascripts": "<2022.4", - "neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", - "neos/form": ">=1.2,<4.3.3|>=5,<5.0.9|>=5.1,<5.1.3", - "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.9.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2", - "neos/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", - "netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15", - "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6", - "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13", - "nilsteampassnet/teampass": "<=2.1.27.36", - "noumo/easyii": "<=0.9", - "nukeviet/nukeviet": "<4.5.2", - "nystudio107/craft-seomatic": "<3.4.12", - "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", - "october/backend": "<1.1.2", - "october/cms": "= 1.1.1|= 1.0.471|= 1.0.469|>=1.0.319,<1.0.469", - "october/october": ">=1.0.319,<1.0.466|>=2.1,<2.1.12", - "october/rain": "<1.0.472|>=1.1,<1.1.2", - "october/system": "<1.0.476|>=1.1,<1.1.12|>=2,<2.2.15", - "onelogin/php-saml": "<2.10.4", - "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5", - "open-web-analytics/open-web-analytics": "<1.7.4", - "opencart/opencart": "<=3.0.3.2", - "openid/php-openid": "<2.3", - "openmage/magento-lts": "<19.4.15|>=20,<20.0.13", - "orchid/platform": ">=9,<9.4.4", - "oro/commerce": ">=5,<5.0.4", - "oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7", - "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<4.2.8", - "packbackbooks/lti-1-3-php-library": "<5", - "padraic/humbug_get_contents": "<1.1.2", - "pagarme/pagarme-php": ">=0,<3", - "pagekit/pagekit": "<=1.0.18", - "paragonie/random_compat": "<2", - "passbolt/passbolt_api": "<2.11", - "paypal/merchant-sdk-php": "<3.12", - "pear/archive_tar": "<1.4.14", - "pear/crypt_gpg": "<1.6.7", - "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1", - "personnummer/personnummer": "<3.0.2", - "phanan/koel": "<5.1.4", - "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", - "phpmailer/phpmailer": "<6.5", - "phpmussel/phpmussel": ">=1,<1.6", - "phpmyadmin/phpmyadmin": "<5.1.3", - "phpoffice/phpexcel": "<1.8", - "phpoffice/phpspreadsheet": "<1.16", - "phpseclib/phpseclib": "<2.0.31|>=3,<3.0.7", - "phpservermon/phpservermon": "<=3.5.2", - "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5,<5.6.3", - "phpwhois/phpwhois": "<=4.2.5", - "phpxmlrpc/extras": "<0.6.1", - "pimcore/data-hub": "<1.2.4", - "pimcore/pimcore": "<10.4.4", - "pocketmine/bedrock-protocol": "<8.0.2", - "pocketmine/pocketmine-mp": ">= 4.0.0-BETA5, < 4.4.2|<4.2.10", - "pressbooks/pressbooks": "<5.18", - "prestashop/autoupgrade": ">=4,<4.10.1", - "prestashop/blockwishlist": ">=2,<2.1.1", - "prestashop/contactform": ">1.0.1,<4.3", - "prestashop/gamification": "<2.3.2", - "prestashop/prestashop": ">=1.6.0.10,<1.7.8.7", - "prestashop/productcomments": ">=4,<4.2.1", - "prestashop/ps_emailsubscription": "<2.6.1", - "prestashop/ps_facetedsearch": "<3.4.1", - "prestashop/ps_linklist": "<3.1", - "privatebin/privatebin": "<1.4", - "propel/propel": ">=2-alpha.1,<=2-alpha.7", - "propel/propel1": ">=1,<=1.7.1", - "pterodactyl/panel": "<1.7", - "ptrofimov/beanstalk_console": "<1.7.14", - "pusher/pusher-php-server": "<2.2.1", - "pwweb/laravel-core": "<=0.3.6-beta", - "rainlab/debugbar-plugin": "<3.1", - "remdex/livehelperchat": "<3.99", - "rmccue/requests": ">=1.6,<1.8", - "robrichards/xmlseclibs": "<3.0.4", - "rudloff/alltube": "<3.0.3", - "s-cart/core": "<6.9", - "s-cart/s-cart": "<6.9", - "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1", - "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", - "scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11", - "sensiolabs/connect": "<4.2.3", - "serluck/phpwhois": "<=4.2.6", - "shopware/core": "<=6.4.9", - "shopware/platform": "<=6.4.9", - "shopware/production": "<=6.3.5.2", - "shopware/shopware": "<=5.7.13", - "shopware/storefront": "<=6.4.8.1", - "shopxo/shopxo": "<2.2.6", - "showdoc/showdoc": "<2.10.4", - "silverstripe/admin": ">=1,<1.8.1", - "silverstripe/assets": ">=1,<1.10.1", - "silverstripe/cms": "<4.3.6|>=4.4,<4.4.4", - "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1", - "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": "<4.10.9", - "silverstripe/graphql": "<3.5.2|>=4-alpha.1,<4-alpha.2|= 4.0.0-alpha1", - "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", - "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", - "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", - "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", - "silverstripe/subsites": ">=2,<2.1.1", - "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", - "silverstripe/userforms": "<3", - "simple-updates/phpwhois": "<=1", - "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4", - "simplesamlphp/simplesamlphp": "<1.18.6", - "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", - "simplito/elliptic-php": "<1.0.6", - "slim/slim": "<2.6", - "smarty/smarty": "<3.1.45|>=4,<4.1.1", - "snipe/snipe-it": "<=6.0.2|>= 6.0.0-RC-1, <= 6.0.0-RC-5", - "socalnick/scn-social-auth": "<1.15.2", - "socialiteproviders/steam": "<1.1", - "spipu/html2pdf": "<5.2.4", - "spoonity/tcpdf": "<6.2.22", - "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", - "ssddanbrown/bookstack": "<22.2.3", - "statamic/cms": "<3.2.39|>=3.3,<3.3.2", - "stormpath/sdk": ">=0,<9.9.99", - "studio-42/elfinder": "<2.1.59", - "subrion/cms": "<=4.2.1", - "sulu/sulu": "= 2.4.0-RC1|<1.6.44|>=2,<2.2.18|>=2.3,<2.3.8", - "swiftmailer/swiftmailer": ">=4,<5.4.5", - "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", - "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", - "sylius/grid-bundle": "<1.10.1", - "sylius/paypal-plugin": ">=1,<1.2.4|>=1.3,<1.3.1", - "sylius/resource-bundle": "<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", - "sylius/sylius": "<1.9.10|>=1.10,<1.10.11|>=1.11,<1.11.2", - "symbiote/silverstripe-multivaluefield": ">=3,<3.0.99", - "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4", - "symbiote/silverstripe-versionedfiles": "<=2.0.3", - "symfont/process": ">=0,<4", - "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", - "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", - "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", - "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<=5.3.14|>=5.4.3,<=5.4.3|>=6.0.3,<=6.0.3|= 6.0.3|= 5.4.3|= 5.3.14", - "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", - "symfony/http-kernel": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.4.13|>=5,<5.1.5|>=5.2,<5.3.12", - "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", - "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", - "symfony/mime": ">=4.3,<4.3.8", - "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/polyfill": ">=1,<1.10", - "symfony/polyfill-php55": ">=1,<1.10", - "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/routing": ">=2,<2.0.19", - "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", - "symfony/security-bundle": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11|>=5.3,<5.3.12", - "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", - "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", - "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", - "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.3.2", - "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", - "symfony/symfony": ">=2,<3.4.49|>=4,<4.4.35|>=5,<5.3.12|>=5.3.14,<=5.3.14|>=5.4.3,<=5.4.3|>=6.0.3,<=6.0.3", - "symfony/translation": ">=2,<2.0.17", - "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", - "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", - "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", - "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", - "t3/dce": ">=2.2,<2.6.2", - "t3g/svg-sanitizer": "<1.0.3", - "tastyigniter/tastyigniter": "<3.3", - "tecnickcom/tcpdf": "<6.2.22", - "terminal42/contao-tablelookupwizard": "<3.3.5", - "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1-beta.1,<2.1.3", - "theonedemon/phpwhois": "<=4.2.5", - "thinkcmf/thinkcmf": "<=5.1.7", - "tinymce/tinymce": "<5.10", - "titon/framework": ">=0,<9.9.99", - "topthink/framework": "<=6.0.12", - "topthink/think": "<=6.0.9", - "topthink/thinkphp": "<=3.2.3", - "tribalsystems/zenario": "<9.2.55826", - "truckersmp/phpwhois": "<=4.3.1", - "twig/twig": "<1.38|>=2,<2.14.11|>=3,<3.3.8", - "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.38|>=9,<9.5.29|>=10,<10.4.29|>=11,<11.5.11", - "typo3/cms-backend": ">=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", - "typo3/cms-core": ">=6.2,<=6.2.56|>=7,<7.6.57|>=8,<8.7.47|>=9,<9.5.35|>=10,<10.4.29|>=11,<11.5.11", - "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", - "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", - "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", - "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", - "typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", - "typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10", - "ua-parser/uap-php": "<3.8", - "unisharp/laravel-filemanager": "<=2.3", - "userfrosting/userfrosting": ">=0.3.1,<4.6.3", - "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", - "vanilla/safecurl": "<0.9.2", - "verot/class.upload.php": "<=1.0.3|>=2,<=2.0.4", - "vrana/adminer": "<4.8.1", - "wallabag/tcpdf": "<6.2.22", - "wanglelecc/laracms": "<=1.0.3", - "web-auth/webauthn-framework": ">=3.3,<3.3.4", - "webcoast/deferred-image-processing": "<1.0.2", - "wikimedia/parsoid": "<0.12.2", - "willdurand/js-translation-bundle": "<2.1.1", - "wintercms/winter": "<1.0.475|>=1.1,<1.1.9", - "woocommerce/woocommerce": "<6.6", - "wp-cli/wp-cli": "<2.5", - "wp-graphql/wp-graphql": "<0.3.5", - "wpanel/wpanel4-cms": "<=4.3.1", - "wwbn/avideo": "<=11.6", - "yeswiki/yeswiki": "<4.1", - "yetiforce/yetiforce-crm": "<6.4", - "yidashi/yii2cmf": "<=2", - "yii2mod/yii2-cms": "<1.9.2", - "yiisoft/yii": ">=1.1.14,<1.1.15", - "yiisoft/yii2": "<2.0.38", - "yiisoft/yii2-bootstrap": "<2.0.4", - "yiisoft/yii2-dev": "<2.0.43", - "yiisoft/yii2-elasticsearch": "<2.0.5", - "yiisoft/yii2-gii": "<2.0.4", - "yiisoft/yii2-jui": "<2.0.4", - "yiisoft/yii2-redis": "<2.0.8", - "yoast-seo-for-typo3/yoast_seo": "<7.2.3", - "yourls/yourls": "<=1.8.2", - "zendesk/zendesk_api_client_php": "<2.2.11", - "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", - "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", - "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", - "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5", - "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3", - "zendframework/zend-diactoros": "<1.8.4", - "zendframework/zend-feed": "<2.10.3", - "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-http": "<2.8.1", - "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", - "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", - "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2", - "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4", - "zendframework/zend-validator": ">=2.3,<2.3.6", - "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", - "zendframework/zendframework": "<=3", - "zendframework/zendframework1": "<1.12.20", - "zendframework/zendopenid": ">=2,<2.0.2", - "zendframework/zendxml": ">=1,<1.0.1", - "zetacomponents/mail": "<1.8.2", - "zf-commons/zfc-user": "<1.2.2", - "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", - "zfr/zfr-oauth2-server-module": "<0.1.2", - "zoujingli/thinkadmin": "<6.0.22" - }, - "type": "metapackage", + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "role": "maintainer" - }, - { - "name": "Ilya Tribusean", - "email": "slash3b@gmail.com", - "role": "maintainer" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { - "issues": "https://github.com/Roave/SecurityAdvisories/issues", - "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" }, "funding": [ { - "url": "https://github.com/Ocramius", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories", - "type": "tidelift" } ], - "time": "2022-08-12T16:04:45+00:00" + "time": "2020-10-26T13:08:54+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -5314,7 +6562,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" }, "funding": [ { @@ -5322,34 +6570,34 @@ "type": "github" } ], - "time": "2020-11-30T08:15:22+00:00" + "time": "2020-09-28T05:30:19+00:00" }, { "name": "sebastian/comparator", - "version": "3.0.3", + "version": "4.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "1071dfcef776a57013124ff35e1fc41ccd294758" + "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758", - "reference": "1071dfcef776a57013124ff35e1fc41ccd294758", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5", + "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5", "shasum": "" }, "require": { - "php": ">=7.1", - "sebastian/diff": "^3.0", - "sebastian/exporter": "^3.1" + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -5388,7 +6636,76 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9" + }, + "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/comparator", + "type": "tidelift" + } + ], + "time": "2025-08-10T06:51:50+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -5396,33 +6713,33 @@ "type": "github" } ], - "time": "2020-11-30T08:04:30+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", - "version": "3.0.3", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211", - "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.0", - "symfony/process": "^2 || ^3.3 || ^4" + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -5454,7 +6771,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -5462,27 +6779,27 @@ "type": "github" } ], - "time": "2020-11-30T07:59:04+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", - "version": "4.2.4", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^7.5" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-posix": "*" @@ -5490,7 +6807,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -5517,7 +6834,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -5525,34 +6842,34 @@ "type": "github" } ], - "time": "2020-11-30T07:53:42+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.4", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db" + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", - "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", "shasum": "" }, "require": { - "php": ">=7.0", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -5582,58 +6899,141 @@ "email": "aharvey@php.net" }, { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.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/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:03:27+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", "keywords": [ - "export", - "exporter" + "global state" ], "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.4" + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.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/sebastian/global-state", + "type": "tidelift" } ], - "time": "2021-11-11T13:51:24+00:00" + "time": "2025-08-10T07:10:35+00:00" }, { - "name": "sebastian/global-state", - "version": "3.0.2", + "name": "sebastian/lines-of-code", + "version": "1.0.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921" + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/de036ec91d55d2a9e0db2ba975b512cdb1c23921", - "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "php": ">=7.2", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" }, "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^8.0" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -5648,17 +7048,15 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.2" + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -5666,34 +7064,34 @@ "type": "github" } ], - "time": "2022-02-10T06:55:38+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", - "version": "3.0.4", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", "shasum": "" }, "require": { - "php": ">=7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -5715,7 +7113,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" }, "funding": [ { @@ -5723,32 +7121,32 @@ "type": "github" } ], - "time": "2020-11-30T07:40:27+00:00" + "time": "2020-10-26T13:12:34+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.2", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -5770,7 +7168,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" }, "funding": [ { @@ -5778,32 +7176,32 @@ "type": "github" } ], - "time": "2020-11-30T07:37:18+00:00" + "time": "2020-10-26T13:14:26+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.1", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0", + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -5830,40 +7228,55 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6" }, "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/recursion-context", + "type": "tidelift" } ], - "time": "2020-11-30T07:34:24+00:00" + "time": "2025-08-10T06:57:39+00:00" }, { "name": "sebastian/resource-operations", - "version": "2.0.2", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -5884,8 +7297,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -5893,32 +7305,32 @@ "type": "github" } ], - "time": "2020-11-30T07:30:19+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", - "version": "1.1.4", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4", - "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.2" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -5941,7 +7353,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/1.1.4" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -5949,29 +7361,29 @@ "type": "github" } ], - "time": "2020-11-30T07:25:11+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + "reference": "c6c1022351a901512170118436c764e473f6de8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -5994,22 +7406,96 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/master" + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "spatie/array-to-xml", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/spatie/array-to-xml.git", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/88b2f3852a922dd73177a68938f8eb2ec70c7224", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "pestphp/pest": "^1.21", + "spatie/pest-plugin-snapshots": "^1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Spatie\\ArrayToXml\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://freek.dev", + "role": "Developer" + } + ], + "description": "Convert an array to xml", + "homepage": "https://github.com/spatie/array-to-xml", + "keywords": [ + "array", + "convert", + "xml" + ], + "support": { + "source": "https://github.com/spatie/array-to-xml/tree/3.4.4" }, - "time": "2016-10-03T07:35:21+00:00" + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-12-15T09:00:41+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.1", + "version": "3.13.5", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", "shasum": "" }, "require": { @@ -6019,18 +7505,13 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" }, "bin": [ - "bin/phpcs", - "bin/phpcbf" + "bin/phpcbf", + "bin/phpcs" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" @@ -6038,34 +7519,132 @@ "authors": [ { "name": "Greg Sherwood", - "role": "lead" + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", - "standards" + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-04T16:30:35+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v6.4.30", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "441c6b69f7222aadae7cbf5df588496d5ee37789" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/441c6b69f7222aadae7cbf5df588496d5ee37789", + "reference": "441c6b69f7222aadae7cbf5df588496d5ee37789", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^5.4|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + "source": "https://github.com/symfony/filesystem/tree/v6.4.30" }, - "time": "2022-06-18T07:21:10+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-26T14:43:45+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -6094,7 +7673,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -6102,28 +7681,28 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2025-11-17T20:03:58+00:00" }, { "name": "vimeo/psalm", - "version": "4.26.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "6998fabb2bf528b65777bf9941920888d23c03ac" + "reference": "b8e96bb617bf59382113b1b56cef751f648a7dc9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/6998fabb2bf528b65777bf9941920888d23c03ac", - "reference": "6998fabb2bf528b65777bf9941920888d23c03ac", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/b8e96bb617bf59382113b1b56cef751f648a7dc9", + "reference": "b8e96bb617bf59382113b1b56cef751f648a7dc9", "shasum": "" }, "require": { - "amphp/amp": "^2.4.2", - "amphp/byte-stream": "^1.5", - "composer/package-versions-deprecated": "^1.8.0", + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "composer-runtime-api": "^2", "composer/semver": "^1.4 || ^2.0 || ^3.0", - "composer/xdebug-handler": "^1.1 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^2.0 || ^3.0", "dnoegel/php-xdg-base-dir": "^0.1.1", "ext-ctype": "*", "ext-dom": "*", @@ -6132,34 +7711,36 @@ "ext-mbstring": "*", "ext-simplexml": "*", "ext-tokenizer": "*", - "felixfbecker/advanced-json-rpc": "^3.0.3", - "felixfbecker/language-server-protocol": "^1.5", + "felixfbecker/advanced-json-rpc": "^3.1", + "felixfbecker/language-server-protocol": "^1.5.3", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^4.13", - "openlss/lib-array2xml": "^1.0", - "php": "^7.1|^8", - "sebastian/diff": "^3.0 || ^4.0", - "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0 || ^6.0", - "symfony/polyfill-php80": "^1.25", - "webmozart/path-util": "^2.3" + "nikic/php-parser": "^5.0.0", + "php": "~8.1.17 || ~8.2.4 || ~8.3.0 || ~8.4.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "spatie/array-to-xml": "^2.17.0 || ^3.0", + "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0" }, "provide": { "psalm/psalm": "self.version" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.2", - "brianium/paratest": "^4.0||^6.0", + "amphp/phpunit-util": "^3", + "bamarni/composer-bin-plugin": "^1.4", + "brianium/paratest": "^6.9", + "dg/bypass-finals": "^1.5", "ext-curl": "*", + "mockery/mockery": "^1.5", + "nunomaduro/mock-final-classes": "^1.1", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpdocumentor/reflection-docblock": "^5", - "phpmyadmin/sql-parser": "5.1.0||dev-master", - "phpspec/prophecy": ">=1.9.0", - "phpunit/phpunit": "^9.0", - "psalm/plugin-phpunit": "^0.16", - "slevomat/coding-standard": "^7.0", - "squizlabs/php_codesniffer": "^3.5", - "symfony/process": "^4.3 || ^5.0 || ^6.0", - "weirdan/prophecy-shim": "^1.0 || ^2.0" + "phpstan/phpdoc-parser": "^1.6", + "phpunit/phpunit": "^9.6", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.19", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.6", + "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0" }, "suggest": { "ext-curl": "In order to send data to shepherd", @@ -6172,20 +7753,19 @@ "psalm-refactor", "psalter" ], - "type": "library", + "type": "project", "extra": { "branch-alias": { - "dev-master": "4.x-dev", - "dev-3.x": "3.x-dev", + "dev-1.x": "1.x-dev", "dev-2.x": "2.x-dev", - "dev-1.x": "1.x-dev" + "dev-3.x": "3.x-dev", + "dev-4.x": "4.x-dev", + "dev-5.x": "5.x-dev", + "dev-6.x": "6.x-dev", + "dev-master": "7.x-dev" } }, "autoload": { - "files": [ - "src/functions.php", - "src/spl_object_id.php" - ], "psr-4": { "Psalm\\": "src/Psalm/" } @@ -6203,87 +7783,29 @@ "keywords": [ "code", "inspection", - "php" + "php", + "static analysis" ], "support": { + "docs": "https://psalm.dev/docs", "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/4.26.0" - }, - "time": "2022-07-31T13:10:26+00:00" - }, - { - "name": "webmozart/path-util", - "version": "2.3.0", - "source": { - "type": "git", - "url": "https://github.com/webmozart/path-util.git", - "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", - "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "webmozart/assert": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\PathUtil\\": "src/" - } + "source": "https://github.com/vimeo/psalm" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", - "support": { - "issues": "https://github.com/webmozart/path-util/issues", - "source": "https://github.com/webmozart/path-util/tree/2.3.0" - }, - "abandoned": "symfony/filesystem", - "time": "2015-12-17T08:42:14+00:00" - } - ], - "aliases": [ - { - "package": "deployer/deployer", - "version": "9999999-dev", - "alias": "v7.0.0", - "alias_normalized": "7.0.0.0" + "time": "2025-01-26T12:03:19+00:00" } ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "deployer/deployer": 20, - "hypernode/deploy-configuration": 20, - "roave/security-advisories": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { + "php": ">=8.1", "ext-json": "*", + "ext-pcntl": "*", "ext-zlib": "*", "composer-runtime-api": "^2" }, - "platform-dev": [], - "plugin-api-version": "2.0.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/docker-compose.yaml b/docker-compose.yaml index 28cb9fb..0772eba 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,8 +1,7 @@ -version: "3" services: hypernode: container_name: hypernode - image: docker.hypernode.com/byteinternet/hypernode-buster-docker-php${PHP_VERSION_SHORT:-81}-mysql57 + image: docker.hypernode.com/byteinternet/hypernode-${IMAGE_OS:-buster}-docker-php${PHP_VERSION_SHORT:-81}-mysql57 volumes: - ./ci/test/.ssh:/root/.ssh ports: diff --git a/gitlab/.gitlab-ci-build.yml b/gitlab/.gitlab-ci-build.yml deleted file mode 100644 index ca3babc..0000000 --- a/gitlab/.gitlab-ci-build.yml +++ /dev/null @@ -1,87 +0,0 @@ -# PHP 7.2 -build:php7-2-node10: - needs: ["quality:dockerfile"] - extends: - - .build - - .variables-php7-2 - - .variables-node10 - -build:php7-2-node12: - needs: ["quality:dockerfile"] - extends: - - .build - - .variables-php7-2 - - .variables-node12 - -build:php7-2-node14: - needs: ["quality:dockerfile"] - extends: - - .build - - .variables-php7-2 - - .variables-node14 - -# PHP 7.3 -build:php7-3-node10: - needs: ["quality:dockerfile"] - extends: - - .build - - .variables-php7-3 - - .variables-node10 - -build:php7-3-node12: - needs: ["quality:dockerfile"] - extends: - - .build - - .variables-php7-3 - - .variables-node12 - -build:php7-3-node14: - needs: ["quality:dockerfile"] - extends: - - .build - - .variables-php7-3 - - .variables-node14 - -# PHP 7.4 -build:php7-4-node10: - needs: ["quality:dockerfile"] - extends: - - .build - - .variables-php7-4 - - .variables-node10 - -build:php7-4-node12: - needs: ["quality:dockerfile"] - extends: - - .build - - .variables-php7-4 - - .variables-node12 - -build:php7-4-node14: - needs: ["quality:dockerfile"] - extends: - - .build - - .variables-php7-4 - - .variables-node14 - -# PHP 8.0 -build:php8-0-node10: - needs: ["quality:dockerfile"] - extends: - - .build - - .variables-php8-0 - - .variables-node10 - -build:php8-0-node12: - needs: ["quality:dockerfile"] - extends: - - .build - - .variables-php8-0 - - .variables-node12 - -build:php8-0-node14: - needs: ["quality:dockerfile"] - extends: - - .build - - .variables-php8-0 - - .variables-node14 diff --git a/gitlab/.gitlab-ci-publish.yml b/gitlab/.gitlab-ci-publish.yml deleted file mode 100644 index 7a2ff45..0000000 --- a/gitlab/.gitlab-ci-publish.yml +++ /dev/null @@ -1,87 +0,0 @@ -# PHP 7.2 -publish:php7-2-node10: - needs: ["test:php7-2-node10"] - extends: - - .publish - - .variables-php7-2 - - .variables-node10 - -publish:php7-2-node12: - needs: ["test:php7-2-node12"] - extends: - - .publish - - .variables-php7-2 - - .variables-node12 - -publish:php7-2-node14: - needs: ["test:php7-2-node14"] - extends: - - .publish - - .variables-php7-2 - - .variables-node14 - -# PHP 7.3 -publish:php7-3-node10: - needs: ["test:php7-3-node10"] - extends: - - .publish - - .variables-php7-3 - - .variables-node10 - -publish:php7-3-node12: - needs: ["test:php7-3-node12"] - extends: - - .publish - - .variables-php7-3 - - .variables-node12 - -publish:php7-3-node14: - needs: ["test:php7-3-node14"] - extends: - - .publish - - .variables-php7-3 - - .variables-node14 - -# PHP 7.4 -publish:php7-4-node10: - needs: ["test:php7-4-node10"] - extends: - - .publish - - .variables-php7-4 - - .variables-node10 - -publish:php7-4-node12: - needs: ["test:php7-4-node12"] - extends: - - .publish - - .variables-php7-4 - - .variables-node12 - -publish:php7-4-node14: - needs: ["test:php7-4-node14"] - extends: - - .publish - - .variables-php7-4 - - .variables-node14 - -# PHP 8.0 -publish:php8-0-node10: - needs: ["test:php8-0-node10"] - extends: - - .publish - - .variables-php8-0 - - .variables-node10 - -publish:php8-0-node12: - needs: ["test:php8-0-node12"] - extends: - - .publish - - .variables-php8-0 - - .variables-node12 - -publish:php8-0-node14: - needs: ["test:php8-0-node14"] - extends: - - .publish - - .variables-php8-0 - - .variables-node14 diff --git a/gitlab/.gitlab-ci-test.yml b/gitlab/.gitlab-ci-test.yml deleted file mode 100644 index 6956640..0000000 --- a/gitlab/.gitlab-ci-test.yml +++ /dev/null @@ -1,87 +0,0 @@ -# PHP 7.2 -test:php7-2-node10: - needs: ["build:php7-2-node10"] - extends: - - .test - - .variables-php7-2 - - .variables-node10 - -test:php7-2-node12: - needs: ["build:php7-2-node12"] - extends: - - .test - - .variables-php7-2 - - .variables-node12 - -test:php7-2-node14: - needs: ["build:php7-2-node14"] - extends: - - .test - - .variables-php7-2 - - .variables-node14 - -# PHP 7.3 -test:php7-3-node10: - needs: ["build:php7-3-node10"] - extends: - - .test - - .variables-php7-3 - - .variables-node10 - -test:php7-3-node12: - needs: ["build:php7-3-node12"] - extends: - - .test - - .variables-php7-3 - - .variables-node12 - -test:php7-3-node14: - needs: ["build:php7-3-node14"] - extends: - - .test - - .variables-php7-3 - - .variables-node14 - -# PHP 7.4 -test:php7-4-node10: - needs: ["build:php7-4-node10"] - extends: - - .test - - .variables-php7-4 - - .variables-node10 - -test:php7-4-node12: - needs: ["build:php7-4-node12"] - extends: - - .test - - .variables-php7-4 - - .variables-node12 - -test:php7-4-node14: - needs: ["build:php7-4-node14"] - extends: - - .test - - .variables-php7-4 - - .variables-node14 - -# PHP 8.0 -test:php8-0-node10: - needs: ["build:php8-0-node10"] - extends: - - .test - - .variables-php8-0 - - .variables-node10 - -test:php8-0-node12: - needs: ["build:php8-0-node12"] - extends: - - .test - - .variables-php8-0 - - .variables-node12 - -test:php8-0-node14: - needs: ["build:php8-0-node14"] - extends: - - .test - - .variables-php8-0 - - .variables-node14 diff --git a/gitlab/.gitlab-ci-variables-node.yml b/gitlab/.gitlab-ci-variables-node.yml deleted file mode 100644 index 1f25ac9..0000000 --- a/gitlab/.gitlab-ci-variables-node.yml +++ /dev/null @@ -1,11 +0,0 @@ -.variables-node10: - variables: - NODE_VERSION: "10" - -.variables-node12: - variables: - NODE_VERSION: "12" - -.variables-node14: - variables: - NODE_VERSION: "14" diff --git a/gitlab/.gitlab-ci-variables-php.yml b/gitlab/.gitlab-ci-variables-php.yml deleted file mode 100644 index f0e5e00..0000000 --- a/gitlab/.gitlab-ci-variables-php.yml +++ /dev/null @@ -1,15 +0,0 @@ -.variables-php7-2: - variables: - PHP_VERSION: "7.2" - -.variables-php7-3: - variables: - PHP_VERSION: "7.3" - -.variables-php7-4: - variables: - PHP_VERSION: "7.4" - -.variables-php8-0: - variables: - PHP_VERSION: "8.0" diff --git a/grumphp.yml b/grumphp.yml index c4d07c9..820fad0 100644 --- a/grumphp.yml +++ b/grumphp.yml @@ -5,9 +5,12 @@ grumphp: warning_severity: 0 whitelist_patterns: - /^src\/(.*)/ + - /^tests\/(.*)/ triggered_by: [php] psalm: config: psalm.xml no_cache: true composer: file: ../composer.json + phpunit: + config_file: phpunit.xml.dist diff --git a/hadolint.yaml b/hadolint.yaml deleted file mode 100644 index 3e0a228..0000000 --- a/hadolint.yaml +++ /dev/null @@ -1,9 +0,0 @@ -ignored: - - DL3018 # tag apk add versions - - DL4006 # set -o pipefail - - SC2039 # POSIX sh [[ ]] - - SC2038 # Use -print0/-0 or -exec + to allow for non-alphanumeric filenames. - - SC2086 # Double quote to prevent globbing and word splitting. - - DL3022 # Allowed to copy from external image - - DL3008 # Do not require specific pinned package versions - - DL3059 # Allow multiple consecutive RUN statements diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..3fd8d77 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,20 @@ + + + + + tests/Unit + + + + + src + + + diff --git a/psalm.xml b/psalm.xml index 136b2eb..c5218c1 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,6 @@ + + + + + + + + + diff --git a/runtests.sh b/runtests.sh index b699fdb..ce7ff37 100755 --- a/runtests.sh +++ b/runtests.sh @@ -1,163 +1,11 @@ #!/usr/bin/env bash -set -e -set -x +ACTION=${1:-general} +RUNNER="ci/test/run-${ACTION}.sh" -export PHP_VERSION_SHORT=$(echo "${PHP_VERSION:-8.1}" | sed 's/\.//') - -# Handy aliases -HN="docker-compose exec -T hypernode" -DP="docker-compose exec -T deploy" -DP1="docker-compose exec --workdir=/web1 -T deploy" -DP2="docker-compose exec --workdir=/web2 -T deploy" - -function install_magento() { - $HN mysql -e "DROP DATABASE IF EXISTS dummytag_preinstalled_magento" - $HN mysql -e "CREATE DATABASE dummytag_preinstalled_magento" - local pw=$($HN bash -c "grep password /data/web/.my.cnf | cut -d' ' -f3") - - # Strip carriage return of pw and saves it in a new variable - pw=$(echo $pw | tr -d '\r') - - $HN bash -c "/data/web/magento2/bin/magento setup:install \ - --base-url=http://banaan1.store \ - --db-host=mysqlmaster.dummytag.hypernode.io \ - --db-name=dummytag_preinstalled_magento --db-user=app \ - --db-password=$pw \ - --admin-firstname=admin --admin-lastname=admin \ - --admin-email=admin@admin.com --admin-user=admin \ - --admin-password=admin123 --language=en_US --currency=USD \ - --timezone=America/Chicago --elasticsearch-host=localhost" -} - -# Install docker-compose if it's not installed -if ! [ -x "$(command -v docker-compose)" ]; then - pip install docker-compose +if [[ ! -f "${RUNNER}" ]]; then + echo "Testsuite runner ${RUNNER} does not exist!" + exit 1 fi -# Clear up env -trap "docker-compose down -v" EXIT - -docker-compose up -d - -# Create working initial Magento install on the Hypernode container -$HN composer create-project --repository=https://mage-os.hypernode.com/mirror/ magento/project-community-edition /data/web/magento2 -echo "Waiting for MySQL to be available on the Hypernode container" -$HN bash -c "until mysql -e 'select 1' ; do sleep 1; done" -install_magento - -# Copy env to the deploy container -$HN /data/web/magento2/bin/magento app:config:dump scopes themes -echo "Waiting for SSH to be available on the Hypernode container" -chmod 0600 ci/test/.ssh/id_rsa -chmod 0600 ci/test/.ssh/authorized_keys -$DP rsync -a app@hypernode:/data/web/magento2/ /web -$DP rsync -v -a /config/ /web -$DP rm /web/app/etc/env.php - -# Create second app -$DP cp -ra /web /web1 -$DP cp -ra /web /web2 - -# Build both apps -$DP1 hypernode-deploy build -v -f /web1/deploy1.php -$DP2 hypernode-deploy build -v -f /web2/deploy2.php - -# Prepare env -$HN mkdir -p /data/web/apps/banaan1.store/shared/app/etc/ -$HN cp /data/web/magento2/app/etc/env.php /data/web/apps/banaan1.store/shared/app/etc/env.php -$HN mkdir -p /data/web/apps/banaan2.store/shared/app/etc/ -$HN cp /data/web/magento2/app/etc/env.php /data/web/apps/banaan2.store/shared/app/etc/env.php -$HN chown -R app:app /data/web/apps - -########################################## -# DEPLOY WITHOUT PLATFORM CONFIGURATIONS # -# This should pass, but not generate any # -# Nginx/Supervisor/etc configs # -########################################## -# SSH from deploy container to hypernode container -$DP1 hypernode-deploy deploy production -f /web1/deploy1_without_platformconfig.php -v - -# Check if deployment made only one release for store1 -test $($HN ls /data/web/apps/banaan1.store/releases/ | wc -l) = 1 - -# Platform configs shouldn't be present yet -$HN test ! -d /data/web/nginx/banaan1.store -$HN test ! -d /data/web/supervisor/banaan1.store -$HN crontab -l -u app | grep "### BEGIN banaan1.store ###" && exit 1 -$HN test ! -d /data/web/varnish/banaan1.store - -################## -# DEPLOY STORE 2 # -################## -# Store 2 -$DP2 hypernode-deploy deploy production -f /web2/deploy2.php -v - -# Check if deployment made only one release for store2 -test $($HN ls /data/web/apps/banaan2.store/releases/ | wc -l) = 1 -$HN ls -al /data/web/nginx/banaan2.store/ -$HN ls -al /data/web/apps/banaan2.store/current/ -$HN ls -al /data/web/apps/banaan2.store/current/nginx/ -$HN test -f /data/web/nginx/banaan2.store/server.example.conf || ($HN ls -al /data/web/nginx && $HN ls -al /data/web/nginx/banaan2.store && exit 1) -$HN test $($HN readlink -f /data/web/nginx/banaan2.store) = /data/web/apps/banaan2.store/releases/1/nginx - -################################## -# DEPLOY PLATFORM CONFIGURATIONS # -# Now we should get revisions of # -# all platform configs. # -################################## -$DP1 hypernode-deploy deploy production -v -f /web1/deploy1.php - -# Check if example location block was placed -$HN ls -al /data/web/nginx/banaan1.store/ -$HN ls -al /data/web/apps/banaan1.store/current/ -$HN ls -al /data/web/apps/banaan1.store/current/nginx/ -$HN test -f /data/web/nginx/banaan1.store/server.example.conf || ($HN ls -al /data/web/nginx && $HN ls -al /data/web/nginx/banaan1.store && exit 1) -$HN test $($HN readlink -f /data/web/nginx/banaan1.store) = /data/web/apps/banaan1.store/releases/2/nginx - -$HN test -f /data/web/supervisor/banaan1.store/example.conf || ($HN ls -al /data/web/supervisor/ && exit 1) -$HN test $($HN readlink -f /data/web/supervisor/banaan1.store) = /data/web/apps/banaan1.store/releases/2/supervisor - -# Test this once we enable supervisor in the hypernode docker image -# $HN supervisorctl status | grep example | grep -v FATAL || ($HN supervisorctl status && exit 1) - -# Test if varnish dirs exists and vcl has been placed -$HN ls -al /data/web/varnish/banaan1.store/ -$HN ls -al /data/web/apps/banaan1.store/current/varnish/ - -$HN test -f /data/web/varnish/banaan1.store/varnish.vcl || ($HN ls -al /data/web/varnish/ && exit 1) -$HN test $($HN readlink -f /data/web/varnish/banaan1.store/varnish.vcl) = /data/web/apps/banaan1.store/releases/2/varnish/varnish.vcl - -# Check the content of the crontab block -$HN crontab -l -u app | grep "### BEGIN banaan1.store ###" -$HN crontab -l -u app | grep "### END banaan1.store ###" -$HN crontab -l -u app | sed -n -e '/### BEGIN banaan1.store ###/,/### END banaan1.store ###/ p' | grep "banaan" - -###################################### -# REMOVE A NGINX LOCATION # -# Create a new release but make sure # -# that the file is removed in the # -# new release. # -###################################### -# Remove example location -$DP rm /web1/etc/nginx/server.example.conf - -# Deploy again -$DP1 hypernode-deploy deploy production -f /web1/deploy1.php - -# Check if another deployment was made -test $($HN ls /data/web/apps/banaan1.store/releases/ | wc -l) = 3 -$HN test $($HN readlink -f /data/web/nginx/banaan1.store) = /data/web/apps/banaan1.store/releases/3/nginx -$HN test $($HN readlink -f /data/web/supervisor/banaan1.store) = /data/web/apps/banaan1.store/releases/3/supervisor -$HN test $($HN readlink -f /data/web/varnish/banaan1.store/varnish.vcl) = /data/web/apps/banaan1.store/releases/3/varnish/varnish.vcl - -# Verify example location block is removed -$HN test ! -f /data/web/nginx/banaan1.store/server.example.conf || ($HN ls -al /data/web/nginx/banaan1.store && exit 1) - -# Check if the second application is still working as intended -test $($HN ls /data/web/apps/banaan2.store/releases/ | wc -l) = 1 -$HN ls -al /data/web/nginx/banaan2.store/ -$HN ls -al /data/web/apps/banaan2.store/current/ -$HN ls -al /data/web/apps/banaan2.store/current/nginx/ -$HN test -f /data/web/nginx/banaan2.store/server.example.conf || ($HN ls -al /data/web/nginx && $HN ls -al /data/web/nginx/banaan2.store && exit 1) -$HN test $($HN readlink -f /data/web/nginx/banaan2.store) = /data/web/apps/banaan2.store/releases/1/nginx +$RUNNER diff --git a/src/Bootstrap.php b/src/Bootstrap.php index c533154..c66598f 100644 --- a/src/Bootstrap.php +++ b/src/Bootstrap.php @@ -55,7 +55,7 @@ private function initContainer(): Container { $builder = new ContainerBuilder(); $builder->useAutowiring(true); - $builder->useAnnotations(true); + $builder->useAttributes(true); $builder->addDefinitions(Di\ConsoleDefinition::getDefinition()); $builder->addDefinitions([ 'version' => $this->getVersion(), @@ -63,6 +63,10 @@ private function initContainer(): Container $container = $builder->build(); + if (!defined('DEPLOYER_VERSION')) { + define("DEPLOYER_VERSION", sprintf("Hypernode Deploy %s", $this->getVersion())); + } + $this->registerTwigLoader($container); return $container; @@ -95,7 +99,7 @@ private function initApplication(Container $container): Application private function registerTwigLoader(Container $container): void { $loader = new FilesystemLoader(__DIR__ . '/Resource/template'); - $twig = new Environment($loader); + $twig = new Environment($loader, ['autoescape' => false]); $container->set(Environment::class, $twig); } diff --git a/src/Brancher/BrancherHypernodeManager.php b/src/Brancher/BrancherHypernodeManager.php new file mode 100644 index 0000000..394c34e --- /dev/null +++ b/src/Brancher/BrancherHypernodeManager.php @@ -0,0 +1,356 @@ +log = $log; + $this->hypernodeClient = $hypernodeClient + ?? HypernodeClientFactory::create(getenv('HYPERNODE_API_TOKEN') ?: ''); + $this->sshPoller = $sshPoller ?? new SshPoller(); + } + + /** + * Query brancher instances for given Hypernode and return the Brancher instance names. + * + * @param string $hypernode The parent hypernode to query the Brancher instances from + * @param string[] $labels Labels to match against, may be empty + * @return string[] The found Brancher instance names + * @throws ResponseException + */ + public function queryBrancherHypernodes(string $hypernode, array $labels = []): array + { + $result = []; + + $hypernodes = $this->hypernodeClient->app->getList([ + 'parent' => $hypernode, + 'type' => 'brancher', + 'destroyed' => 'False', + ]); + foreach ($hypernodes as $brancher) { + $match = true; + + foreach ($labels as $label) { + if (!in_array($label, $brancher->labels)) { + $match = false; + break; + } + } + + if ($match) { + $result[] = $brancher->name; + } + } + + return $result; + } + + /** + * Query brancher instances for the given Hypernode and label and return the + * most recent Brancher instance name. + * + * @param string $hypernode The parent hypernode to query the Brancher instances from + * @param string[] $labels Labels to match against, may be empty + * @return string|null The found Brancher instance name, or null if none was found + */ + public function reuseExistingBrancherHypernode(string $hypernode, array $labels = []): ?string + { + try { + $brancherHypernodes = $this->queryBrancherHypernodes($hypernode, $labels); + if (count($brancherHypernodes) > 0) { + // Return the last brancher Hypernode, which is the most recently created one + return $brancherHypernodes[count($brancherHypernodes) - 1]; + } + } catch (ResponseException $e) { + $this->log->error( + sprintf( + 'Got an API exception (code %d) while querying for existing brancher Hypernodes for Hypernode %s with labels (%s).', + $e->getCode(), + $hypernode, + implode(', ', $labels) + ) + ); + } + + return null; + } + + /** + * Create brancher Hypernode instance for given Hypernode. + * + * @param string $hypernode Name of the Hypernode + * @param string[] $data Extra data to be applied to brancher instance + * @return string Name of the created brancher Hypernode + * @throws HypernodeApiClientException + * @throws HypernodeApiServerException + */ + public function createForHypernode(string $hypernode, array $data = []): string + { + return $this->hypernodeClient->brancherApp->create($hypernode, $data); + } + + /** + * Wait for brancher Hypernode to become available. + * + * This method first attempts a quick SSH connectivity check. If the brancher is already + * reachable (e.g., when reusing an existing brancher), it returns early. Otherwise, it + * falls back to polling the API logbook for delivery status, then performs a final SSH + * reachability check. + * + * @param string $brancherHypernode Name of the brancher Hypernode + * @param int $timeout Maximum time to wait for availability + * @param int $reachabilityCheckCount Number of consecutive successful checks required + * @param int $reachabilityCheckInterval Seconds between reachability checks + * @return void + * @throws CreateBrancherHypernodeFailedException + * @throws HypernodeApiClientException + * @throws HypernodeApiServerException + * @throws TimeoutException + */ + public function waitForAvailability( + string $brancherHypernode, + int $timeout = 1500, + int $reachabilityCheckCount = 6, + int $reachabilityCheckInterval = 10 + ): void { + $latest = $this->sshPoller->microtime(); + $timeElapsed = 0.0; + + // Phase 1: SSH-first check, early return for reused delivered branchers + $this->log->info( + sprintf('Attempting SSH connectivity check for brancher Hypernode %s...', $brancherHypernode) + ); + + $isReachable = $this->pollSshConnectivity( + $brancherHypernode, + self::PRE_POLL_SUCCESS_COUNT, + self::PRE_POLL_FAIL_COUNT, + $reachabilityCheckInterval, + $timeElapsed, + $latest, + $timeout + ); + if ($isReachable) { + $this->log->info( + sprintf('Brancher Hypernode %s is reachable!', $brancherHypernode) + ); + return; + } + + $this->log->info( + sprintf( + 'SSH check inconclusive for brancher Hypernode %s, falling back to delivery check...', + $brancherHypernode + ) + ); + + // Phase 2: Wait for delivery by polling the logbook + $resolved = false; + $interval = 3; + $allowedErrorWindow = 3; + $logbookStartTime = $timeElapsed; + + while ($timeElapsed < $timeout) { + $now = $this->sshPoller->microtime(); + $timeElapsed += $now - $latest; + $latest = $now; + + try { + $flows = $this->hypernodeClient->logbook->getList($brancherHypernode); + $relevantFlows = array_filter( + $flows, + fn(Flow $flow) => in_array($flow->name, self::RELEVANT_FLOW_NAMES, true) + ); + $failedFlows = array_filter($relevantFlows, fn(Flow $flow) => $flow->isReverted()); + $completedFlows = array_filter($relevantFlows, fn(Flow $flow) => $flow->isComplete()); + + if (count($relevantFlows) > 0 && count($failedFlows) === count($relevantFlows)) { + throw new CreateBrancherHypernodeFailedException(); + } + + if ($relevantFlows && count($completedFlows) === count($relevantFlows)) { + $resolved = true; + break; + } + } catch (HypernodeApiClientException $e) { + // A 404 not found means there are no flows in the logbook yet, we should wait. + // Otherwise, there's an error, and it should be propagated. + if ($e->getCode() !== 404) { + throw $e; + } elseif (($timeElapsed - $logbookStartTime) < $allowedErrorWindow) { + // Sometimes we get an error where the logbook is not yet available, but it will be soon. + // We allow a small window for this to happen, and then we continue polling. + $this->log->info( + sprintf( + 'Got an expected exception during the allowed error window of HTTP code %d, waiting for %s to become available.', + $e->getCode(), + $brancherHypernode + ) + ); + } + } + + $this->sshPoller->sleep($interval); + } + + if (!$resolved) { + throw new TimeoutException( + sprintf('Timed out waiting for brancher Hypernode %s to be delivered', $brancherHypernode) + ); + } + + $this->log->info( + sprintf( + 'Brancher Hypernode %s was delivered. Now waiting for node to become reachable...', + $brancherHypernode + ) + ); + + // Phase 3: Final SSH reachability check + $isReachable = $this->pollSshConnectivity( + $brancherHypernode, + $reachabilityCheckCount, + 0, // No max failures, rely on timeout + $reachabilityCheckInterval, + $timeElapsed, + $latest, + $timeout + ); + if (!$isReachable) { + throw new TimeoutException( + sprintf('Timed out waiting for brancher Hypernode %s to become reachable', $brancherHypernode) + ); + } + + $this->log->info( + sprintf('Brancher Hypernode %s became reachable!', $brancherHypernode) + ); + } + + /** + * Poll SSH connectivity until we get enough consecutive successes or hit a limit. + * + * @param string $brancherHypernode Hostname to check + * @param int $requiredConsecutiveSuccesses Number of consecutive successes required + * @param int $maxFailedAttempts Maximum failed attempts before giving up (0 = no limit, use timeout only) + * @param int $checkInterval Seconds between checks + * @param float $timeElapsed Reference to track elapsed time + * @param float $latest Reference to track latest timestamp + * @param int $timeout Maximum time allowed + * @return bool True if SSH check succeeded, false if we should fall back to other methods + */ + private function pollSshConnectivity( + string $brancherHypernode, + int $requiredConsecutiveSuccesses, + int $maxFailedAttempts, + int $checkInterval, + float &$timeElapsed, + float &$latest, + int $timeout + ): bool { + $consecutiveSuccesses = 0; + $failedAttempts = 0; + + while ($timeElapsed < $timeout) { + $now = $this->sshPoller->microtime(); + $timeElapsed += $now - $latest; + $latest = $now; + + // Check if we've hit the max failed attempts limit (0 = unlimited) + if ($maxFailedAttempts > 0 && $failedAttempts >= $maxFailedAttempts) { + return false; + } + + if ($this->sshPoller->poll($brancherHypernode)) { + $consecutiveSuccesses++; + $this->log->info( + sprintf( + 'Brancher Hypernode %s reachability check %d/%d succeeded.', + $brancherHypernode, + $consecutiveSuccesses, + $requiredConsecutiveSuccesses + ) + ); + + if ($consecutiveSuccesses >= $requiredConsecutiveSuccesses) { + return true; + } + } else { + if ($consecutiveSuccesses > 0) { + $this->log->info( + sprintf( + 'Brancher Hypernode %s reachability check failed, resetting counter (was at %d/%d).', + $brancherHypernode, + $consecutiveSuccesses, + $requiredConsecutiveSuccesses + ) + ); + } + $consecutiveSuccesses = 0; + $failedAttempts++; + } + + $this->sshPoller->sleep($checkInterval); + } + + return false; + } + + /** + * Cancel one or multiple brancher Hypernodes. + * + * @param string ...$brancherHypernodes Name(s) of the brancher Hypernode(s) + * @throws HypernodeApiClientException + * @throws HypernodeApiServerException + */ + public function cancel(string ...$brancherHypernodes): void + { + foreach ($brancherHypernodes as $brancherHypernode) { + $this->log->info(sprintf('Stopping brancher Hypernode %s...', $brancherHypernode)); + try { + $this->hypernodeClient->brancherApp->cancel($brancherHypernode); + } catch (HypernodeApiClientException $e) { + // If the brancher is already cancelled or not found, that's fine - + // our goal was to cancel it anyway + if ($e->getCode() === 404 || str_contains($e->getMessage(), 'has already been cancelled')) { + $this->log->info(sprintf( + 'Brancher Hypernode %s was already cancelled or not found, skipping.', + $brancherHypernode + )); + continue; + } + throw $e; + } + } + } +} diff --git a/src/Brancher/SshPoller.php b/src/Brancher/SshPoller.php new file mode 100644 index 0000000..33c4526 --- /dev/null +++ b/src/Brancher/SshPoller.php @@ -0,0 +1,44 @@ +deployRunner->run($output, 'build', 'build'); - return 0; + return $this->deployRunner->run($output, 'build', DeployRunner::TASK_BUILD, true, false, false); } } diff --git a/src/Command/Cleanup.php b/src/Command/Cleanup.php new file mode 100644 index 0000000..946f39c --- /dev/null +++ b/src/Command/Cleanup.php @@ -0,0 +1,111 @@ +reportLoader = $reportLoader; + $this->deployerLoader = $deployerLoader; + $this->configurationLoader = $configurationLoader; + $this->brancherHypernodeManager = $brancherHypernodeManager; + $this->logger = $logger; + } + + protected function configure() + { + parent::configure(); + $this->setName('cleanup'); + $this->setDescription( + 'Clean up any acquired resources during the deployment, like brancher Hypernodes.' + ); + $this->addArgument('stage', InputArgument::OPTIONAL, 'Stage to cleanup'); + } + + /** + * @throws Throwable + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $report = $this->reportLoader->loadReport(); + + if ($report) { + $this->brancherHypernodeManager->cancel(...$report->getBrancherHypernodes()); + } + + /** @var string $stageName */ + $stageName = $input->getArgument('stage'); + if ($stageName) { + $this->deployerLoader->getOrCreateInstance($output); + $config = $this->configurationLoader->load($input->getOption('file') ?: 'deploy.php'); + $this->cancelByStage($stageName, $config); + } + + return 0; + } + + /** + * Cancel brancher nodes by stage and their configured labels. + * + * @param string $stageName Stage to clean up + * @param Configuration $config Deployment configuration to read stages/servers from + * @return void + */ + private function cancelByStage(string $stageName, Configuration $config): void + { + foreach ($config->getStages() as $stage) { + if ($stage->getName() !== $stageName) { + continue; + } + foreach ($stage->getServers() as $server) { + if (!($server instanceof BrancherServer)) { + continue; + } + $labels = $server->getLabels(); + $hypernode = $server->getOptions()[Server::OPTION_HN_PARENT_APP]; + $this->logger->debug( + sprintf( + 'Cleaning up Brancher instances based on Hypernode %s with labels [%s]', + $hypernode, + implode(', ', $labels), + ) + ); + $brancherHypernodes = $this->brancherHypernodeManager->queryBrancherHypernodes( + $hypernode, + $labels + ); + $this->brancherHypernodeManager->cancel(...$brancherHypernodes); + } + } + } +} diff --git a/src/Command/ComposerAuth.php b/src/Command/ComposerAuth.php index e1cc1dd..524faff 100644 --- a/src/Command/ComposerAuth.php +++ b/src/Command/ComposerAuth.php @@ -34,7 +34,6 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $this->deployRunner->run($output, 'build', 'deploy:vendors:auth'); - return 0; + return $this->deployRunner->run($output, 'build', 'deploy:vendors:auth', false, false, false); } } diff --git a/src/Command/Deploy.php b/src/Command/Deploy.php index 767b49b..1e6edfe 100644 --- a/src/Command/Deploy.php +++ b/src/Command/Deploy.php @@ -3,23 +3,24 @@ namespace Hypernode\Deploy\Command; use Hypernode\Deploy\DeployRunner; +use Hypernode\Deploy\Report\ReportWriter; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Throwable; class Deploy extends Command { - /** - * @var DeployRunner - */ - private $deployRunner; + private DeployRunner $deployRunner; + private ReportWriter $reportWriter; - public function __construct(DeployRunner $deployRunner) + public function __construct(DeployRunner $deployRunner, ReportWriter $reportWriter) { parent::__construct(); $this->deployRunner = $deployRunner; + $this->reportWriter = $reportWriter; } protected function configure() @@ -28,6 +29,12 @@ protected function configure() $this->setName('deploy'); $this->setDescription('Deploy application.'); $this->addArgument('stage', InputArgument::REQUIRED, 'Stage deploy to'); + $this->addOption( + 'reuse-brancher', + null, + InputOption::VALUE_NONE, + 'Reuse the brancher Hypernode from the previous deploy. Only works when using addBrancherServer in your deploy configuration.' + ); } /** @@ -35,7 +42,19 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $this->deployRunner->run($output, $input->getArgument('stage'), 'deploy'); - return 0; + $result = $this->deployRunner->run( + $output, + $input->getArgument('stage'), + DeployRunner::TASK_DEPLOY, + false, + true, + $input->getOption('reuse-brancher') + ); + + if ($result === 0) { + $this->reportWriter->write($this->deployRunner->getDeploymentReport()); + } + + return $result; } } diff --git a/src/Command/RunTask.php b/src/Command/RunTask.php index a25616f..18651d4 100644 --- a/src/Command/RunTask.php +++ b/src/Command/RunTask.php @@ -6,11 +6,17 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Throwable; class RunTask extends Command { + private const ARGUMENT_STAGE = 'stage'; + private const ARGUMENT_TASK = 'task'; + private const OPTION_CONFIGURE_BUILD_STAGE = 'configure-build-stage'; + private const OPTION_CONFIGURE_SERVERS = 'configure-servers'; + /** * @var DeployRunner */ @@ -27,8 +33,10 @@ protected function configure() parent::configure(); $this->setName('run-task'); $this->setDescription('Run a seperate deployer task'); - $this->addArgument('stage', InputArgument::REQUIRED, 'Stage'); - $this->addArgument('task', InputArgument::REQUIRED, 'Task to run'); + $this->addArgument(self::ARGUMENT_STAGE, InputArgument::REQUIRED, 'Stage'); + $this->addArgument(self::ARGUMENT_TASK, InputArgument::REQUIRED, 'Task to run'); + $this->addOption(self::OPTION_CONFIGURE_BUILD_STAGE, 'b', InputOption::VALUE_NONE, 'Configure build stage before running task'); + $this->addOption(self::OPTION_CONFIGURE_SERVERS, 's', InputOption::VALUE_NONE, 'Configure servers before running task'); } /** @@ -36,7 +44,13 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $this->deployRunner->run($output, $input->getArgument('stage'), $input->getArgument('task')); - return 0; + return $this->deployRunner->run( + $output, + $input->getArgument(self::ARGUMENT_STAGE), + $input->getArgument(self::ARGUMENT_TASK), + $input->getOption(self::OPTION_CONFIGURE_BUILD_STAGE), + $input->getOption(self::OPTION_CONFIGURE_SERVERS), + false + ); } } diff --git a/src/Command/Tree.php b/src/Command/Tree.php new file mode 100644 index 0000000..f3cb121 --- /dev/null +++ b/src/Command/Tree.php @@ -0,0 +1,42 @@ +deployRunner = $deployRunner; + } + + protected function execute(Input $input, Output $output): int + { + try { + $this->deployRunner->prepare(true, true, $input->getArgument('task'), false); + } catch (InvalidConfigurationException | ValidationException $e) { + $output->write($e->getMessage()); + return 1; + } + $result = parent::execute($input, $output); + + return $result; + } +} diff --git a/src/ConfigurationLoader.php b/src/ConfigurationLoader.php new file mode 100644 index 0000000..8891c8c --- /dev/null +++ b/src/ConfigurationLoader.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Hypernode\Deploy\Console\Output; + +use Psr\Log\AbstractLogger; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * PSR-3 compliant console logger. + * + * @author Kévin Dunglas + * + * @see https://www.php-fig.org/psr/psr-3/ + */ +class ConsoleLogger extends AbstractLogger +{ + public const INFO = 'info'; + public const ERROR = 'error'; + + private OutputInterface $output; + private array $verbosityLevelMap = [ + LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, + LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, + LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, + LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL, + LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL, + LogLevel::DEBUG => OutputInterface::VERBOSITY_VERBOSE, + ]; + private array $formatLevelMap = [ + LogLevel::EMERGENCY => self::ERROR, + LogLevel::ALERT => self::ERROR, + LogLevel::CRITICAL => self::ERROR, + LogLevel::ERROR => self::ERROR, + LogLevel::WARNING => self::INFO, + LogLevel::NOTICE => self::INFO, + LogLevel::INFO => self::INFO, + LogLevel::DEBUG => self::INFO, + ]; + private bool $errored = false; + + public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = []) + { + $this->output = $output; + $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; + $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; + } + + /** + * {@inheritdoc} + * + * @return void + */ + public function log($level, $message, array $context = []) + { + if (!isset($this->verbosityLevelMap[$level])) { + throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); + } + + $output = $this->output; + + // Write to the error output if necessary and available + if (self::ERROR === $this->formatLevelMap[$level]) { + if ($this->output instanceof ConsoleOutputInterface) { + $output = $this->output->getErrorOutput(); + } + $this->errored = true; + } + + // the if condition check isn't necessary -- it's the same one that $output will do internally anyway. + // We only do it for efficiency here as the message formatting is relatively expensive. + if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { + $output->writeln( + sprintf( + '<%1$s>[%2$s] %3$s', + $this->formatLevelMap[$level], + $level, + $this->interpolate($message, $context) + ), + $this->verbosityLevelMap[$level] + ); + } + } + + /** + * Returns true when any messages have been logged at error levels. + * + * @return bool + */ + public function hasErrored() + { + return $this->errored; + } + + /** + * Interpolates context values into the message placeholders. + * + * @author PHP Framework Interoperability Group + */ + private function interpolate(string $message, array $context): string + { + if (!str_contains($message, '{')) { + return $message; + } + + $replacements = []; + foreach ($context as $key => $val) { + if (null === $val || \is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) { + $replacements["{{$key}}"] = $val; + } elseif ($val instanceof \DateTimeInterface) { + $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); + } elseif (\is_object($val)) { + $replacements["{{$key}}"] = '[object ' . \get_class($val) . ']'; + } else { + $replacements["{{$key}}"] = '[' . \gettype($val) . ']'; + } + } + + return strtr($message, $replacements); + } +} diff --git a/src/DeployRunner.php b/src/DeployRunner.php index 2b736e1..4bb2b15 100644 --- a/src/DeployRunner.php +++ b/src/DeployRunner.php @@ -5,108 +5,129 @@ use Deployer\Deployer; use Deployer\Exception\Exception; use Deployer\Exception\GracefulShutdownException; -use Hypernode\Deploy\Console\Output\OutputWatcher; +use Deployer\Host\Host; +use Deployer\Task\Task; +use Hypernode\Deploy\Brancher\BrancherHypernodeManager; use Hypernode\Deploy\Deployer\RecipeLoader; -use Hypernode\Deploy\Exception\InvalidConfigurationException; use Hypernode\Deploy\Deployer\Task\ConfigurableTaskInterface; use Hypernode\Deploy\Deployer\Task\TaskFactory; +use Hypernode\Deploy\Exception\CreateBrancherHypernodeFailedException; +use Hypernode\Deploy\Exception\InvalidConfigurationException; +use Hypernode\Deploy\Exception\TimeoutException; +use Hypernode\Deploy\Exception\ValidationException; +use Hypernode\DeployConfiguration\Configurable\ServerRoleConfigurableInterface; +use Hypernode\DeployConfiguration\Configurable\StageConfigurableInterface; use Hypernode\DeployConfiguration\Configuration; use Hypernode\DeployConfiguration\Server; -use Hypernode\DeployConfiguration\ServerRoleConfigurableInterface; use Hypernode\DeployConfiguration\Stage; -use Hypernode\DeployConfiguration\StageConfigurableInterface; +use Hypernode\DeployConfiguration\TaskConfigurationInterface; use Psr\Log\LoggerInterface; -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Throwable; use function Deployer\host; use function Deployer\localhost; use function Deployer\run; -use function Deployer\task; class DeployRunner { - /** - * @var TaskFactory - */ - private $taskFactory; + public const TASK_BUILD = 'build'; + public const TASK_DEPLOY = 'deploy'; - /** - * @var InputInterface - */ - private $input; + public const DEFAULT_BRANCHER_TIMEOUT = 1500; + public const DEFAULT_BRANCHER_REACHABILITY_CHECK_COUNT = 6; + public const DEFAULT_BRANCHER_REACHABILITY_CHECK_INTERVAL = 10; - /** - * @var LoggerInterface - */ - private $log; + private TaskFactory $taskFactory; + private InputInterface $input; + private LoggerInterface $log; + private RecipeLoader $recipeLoader; + private DeployerLoader $deployerLoader; + private ConfigurationLoader $configurationLoader; + private BrancherHypernodeManager $brancherHypernodeManager; /** - * @var RecipeLoader + * Registered brancher Hypernodes to stop/cancel after running. + * + * @var string[] */ - private $recipeLoader; + private array $brancherHypernodesRegistered = []; + + private array $deployedHostnames = []; + private string $deployedStage = ''; public function __construct( TaskFactory $taskFactory, InputInterface $input, LoggerInterface $log, - RecipeLoader $recipeLoader + RecipeLoader $recipeLoader, + DeployerLoader $deployerLoader, + ConfigurationLoader $configurationLoader, + BrancherHypernodeManager $brancherHypernodeManager ) { $this->taskFactory = $taskFactory; $this->input = $input; $this->log = $log; $this->recipeLoader = $recipeLoader; + $this->deployerLoader = $deployerLoader; + $this->configurationLoader = $configurationLoader; + $this->brancherHypernodeManager = $brancherHypernodeManager; } /** * @throws GracefulShutdownException * @throws Throwable * @throws Exception - * - * @return void */ - public function run(OutputInterface $output, string $stage, string $task = 'deploy') - { - $console = new Application(); - $deployer = new Deployer($console); - $deployer['output'] = new OutputWatcher($output); - $deployer['input'] = new ArrayInput( - [], - new InputDefinition([ - new InputOption('limit'), - new InputOption('profile'), - ]) - ); + public function run( + OutputInterface $output, + string $stage, + string $task, + bool $configureBuildStage, + bool $configureServers, + bool $reuseBrancher + ): int { + $deployer = $this->deployerLoader->getOrCreateInstance($output); try { - $this->initializeDeployer($deployer); - } catch (InvalidConfigurationException $e) { + $this->prepare($configureBuildStage, $configureServers, $stage, $reuseBrancher); + } catch (InvalidConfigurationException | ValidationException $e) { $output->write($e->getMessage()); - return; + return 1; } - $this->runStage($deployer, $stage, $task); + + return $this->runStage($deployer, $stage, $task); } /** - * Initialize deployer settings + * Prepare deploy runner before running stage * * @throws Exception * @throws GracefulShutdownException - * @throws Throwable * @throws InvalidConfigurationException + * @throws Throwable */ - private function initializeDeployer(Deployer $deployer): void - { + public function prepare( + bool $configureBuildStage, + bool $configureServers, + string $stage, + bool $reuseBrancher + ): void { $this->recipeLoader->load('common.php'); $tasks = $this->taskFactory->loadAll(); - $config = $this->getConfiguration($deployer); + $config = $this->configurationLoader->load( + $this->input->getOption('file') ?: 'deploy.php' + ); $config->setLogger($this->log); - $this->configureStages($config); + + if ($configureBuildStage) { + $this->initializeBuildStage($config); + } + + if ($configureServers) { + $this->configureServers($config, $stage, $reuseBrancher); + } foreach ($tasks as $task) { $task->configure($config); @@ -130,7 +151,7 @@ private function initializeDeployer(Deployer $deployer): void } /** - * Configure deploy tasks based on specific configuration in Hipex deploy configuration + * Configure deploy tasks based on specific configuration in Hypernode Deploy configuration * @throws InvalidConfigurationException */ private function initializeConfigurableTask(ConfigurableTaskInterface $task, Configuration $mainConfig): void @@ -141,54 +162,62 @@ private function initializeConfigurableTask(ConfigurableTaskInterface $task, Con ); foreach ($configurations as $taskConfig) { - if (!$task->supports($taskConfig)) { - continue; - } - - $deployerTask = $task->configureWithTaskConfig($taskConfig); - if ($deployerTask) { - if ($taskConfig instanceof StageConfigurableInterface && $taskConfig->getStage()) { - $deployerTask->select("stage={$taskConfig->getStage()->getName()}"); - } + if ($task->supports($taskConfig)) { + $deployerTask = $task->configureWithTaskConfig($taskConfig); - if ($taskConfig instanceof ServerRoleConfigurableInterface && $taskConfig->getServerRoles()) { - $roles = implode("&", $taskConfig->getServerRoles()); - $deployerTask->select("roles={$roles}"); + if ($deployerTask) { + $this->configureDeployerTask($deployerTask, $taskConfig); } } } } - /** - * @throws Exception - * @throws GracefulShutdownException - * @throws Throwable - */ - private function getConfiguration(Deployer $deployer): Configuration + private function configureDeployerTask(Task $deployerTask, TaskConfigurationInterface $taskConfig): void { - try { - return $this->tryGetConfiguration(); - } catch (\Throwable $e) { - $this->log->warning(sprintf('Failed to initialize deploy.php configuration file: %s', $e->getMessage())); - $this->tryComposerInstall($deployer); - $this->initializeAppAutoloader(); - return $this->tryGetConfiguration(); + $roles = $taskConfig instanceof ServerRoleConfigurableInterface + ? $taskConfig->getServerRoles() + : []; + $stage = $taskConfig instanceof StageConfigurableInterface && $taskConfig->getStage() + ? $taskConfig->getStage()->getName() + : null; + + if ($roles) { + if ($stage) { + $deployerTask->select( + sprintf( + "stage={$stage}&roles=%s", + implode(",stage={$stage}&roles=", $roles) + ) + ); + } else { + $deployerTask->select('roles=' . implode(',roles=', $roles)); + } + } elseif ($stage) { + $deployerTask->select("stage={$stage}"); } } - private function configureStages(Configuration $config): void + private function configureServers(Configuration $config, string $stage, bool $reuseBrancher): void { - $this->initializeBuildStage($config); + foreach ($config->getStages() as $configStage) { + if ($configStage->getName() !== $stage) { + continue; + } - foreach ($config->getStages() as $stage) { - foreach ($stage->getServers() as $server) { - $this->configureStageServer($stage, $server, $config); + foreach ($configStage->getServers() as $server) { + $this->configureStageServer($configStage, $server, $config, $reuseBrancher); } } } - private function configureStageServer(Stage $stage, Server $server, Configuration $config): void - { + private function configureStageServer( + Stage $stage, + Server $server, + Configuration $config, + bool $reuseBrancher + ): void { + $this->maybeConfigureBrancherServer($server, $reuseBrancher); + $host = host($stage->getName() . ':' . $server->getHostname()); $host->setHostname($server->getHostname()); $host->setPort(22); @@ -198,20 +227,19 @@ private function configureStageServer(Stage $stage, Server $server, Configuratio $host->setSshMultiplexing(true); $host->set('roles', $server->getRoles()); $host->set('domain', $stage->getDomain()); + $host->set('stage', $stage->getName()); $host->set('deploy_path', function () { // Ensure directory exists before returning it run('mkdir -p ~/apps/{{domain}}/shared'); return run('realpath ~/apps/{{domain}}'); }); $host->set('current_path', '{{deploy_path}}/current'); - $host->set('app_release_path', '{{release_path}}/app'); - $host->set('app_current_path', '{{current_path}}/app'); - $host->set('nginx_release_path', '{{release_path}}/nginx'); - $host->set('nginx_current_path', '{{current_path}}/nginx'); - $host->set('supervisor_release_path', '{{release_path}}/supervisor'); - $host->set('supervisor_current_path', '{{current_path}}/supervisor'); - $host->set('varnish_release_path', '{{release_path}}/varnish'); - $host->set('varnish_current_path', '{{current_path}}/varnish'); + $host->set('nginx_release_path', '{{release_path}}/.hypernode/nginx'); + $host->set('nginx_current_path', '{{current_path}}/.hypernode/nginx'); + $host->set('supervisor_release_path', '{{release_path}}/.hypernode/supervisor'); + $host->set('supervisor_current_path', '{{current_path}}/.hypernode/supervisor'); + $host->set('varnish_release_path', '{{release_path}}/.hypernode/varnish'); + $host->set('varnish_current_path', '{{current_path}}/.hypernode/varnish'); $host->set('configuration_stage', $stage); $host->set('writable_mode', 'chmod'); @@ -242,6 +270,60 @@ private function configureStageServer(Stage $stage, Server $server, Configuratio } } + private function maybeConfigureBrancherServer(Server $server, bool $reuseBrancher): void + { + $serverOptions = $server->getOptions(); + $isBrancher = $serverOptions[Server::OPTION_HN_BRANCHER] ?? false; + $parentApp = $serverOptions[Server::OPTION_HN_PARENT_APP] ?? null; + if ($isBrancher && $parentApp) { + $settings = $serverOptions[Server::OPTION_HN_BRANCHER_SETTINGS] ?? []; + $labels = $serverOptions[Server::OPTION_HN_BRANCHER_LABELS] ?? []; + $timeout = $serverOptions[Server::OPTION_HN_BRANCHER_TIMEOUT] ?? self::DEFAULT_BRANCHER_TIMEOUT; + $reachabilityCheckCount = $serverOptions[Server::OPTION_HN_BRANCHER_REACHABILITY_CHECK_COUNT] ?? self::DEFAULT_BRANCHER_REACHABILITY_CHECK_COUNT; + $reachabilityCheckInterval = $serverOptions[Server::OPTION_HN_BRANCHER_REACHABILITY_CHECK_INTERVAL] ?? self::DEFAULT_BRANCHER_REACHABILITY_CHECK_INTERVAL; + + $this->log->info(sprintf('Creating an brancher Hypernode based on %s.', $parentApp)); + if ($settings) { + $this->log->info( + sprintf('Settings to be applied: [%s].', implode(', ', $settings)) + ); + } + if ($labels) { + $this->log->info( + sprintf('Labels to be applied: [%s].', implode(', ', $labels)) + ); + } + + $data = $settings; + $data['labels'] = $labels; + if ($reuseBrancher && $brancherApp = $this->brancherHypernodeManager->reuseExistingBrancherHypernode($parentApp, $labels)) { + $this->log->info(sprintf('Found existing brancher Hypernode, name is %s.', $brancherApp)); + } else { + $brancherApp = $this->brancherHypernodeManager->createForHypernode($parentApp, $data); + $this->log->info(sprintf('Successfully requested brancher Hypernode, name is %s.', $brancherApp)); + $this->brancherHypernodesRegistered[] = $brancherApp; + } + + try { + $this->log->info('Waiting for brancher Hypernode to become available...'); + $this->brancherHypernodeManager->waitForAvailability( + $brancherApp, + $timeout, + $reachabilityCheckCount, + $reachabilityCheckInterval + ); + $this->log->info('Brancher Hypernode has become available!'); + } catch (CreateBrancherHypernodeFailedException | TimeoutException $e) { + if (in_array($brancherApp, $this->brancherHypernodesRegistered)) { + $this->brancherHypernodeManager->cancel($brancherApp); + } + + throw $e; + } + $server->setHostname(sprintf("%s.hypernode.io", $brancherApp)); + } + } + /** * Initialize build stage */ @@ -273,7 +355,7 @@ private function initializeBuildStage(Configuration $config): void * @throws Throwable * @throws Exception */ - private function runStage(Deployer $deployer, string $stage, string $task = 'deploy'): void + private function runStage(Deployer $deployer, string $stage, string $task = 'deploy'): int { $hosts = $deployer->selector->select("stage=$stage"); if (empty($hosts)) { @@ -283,88 +365,48 @@ private function runStage(Deployer $deployer, string $stage, string $task = 'dep $tasks = $deployer->scriptManager->getTasks($task); $executor = $deployer->master; - try { - /** - * Set the env variable to tell deployer to deploy the hosts sequentially instead of parallel. - * @see \Deployer\Executor\Master::runTask() - */ - putenv('DEPLOYER_LOCAL_WORKER=true'); - $executor->run($tasks, $hosts); - } catch (Throwable $exception) { - $deployer->output->writeln('[' . \get_class($exception) . '] ' . $exception->getMessage()); - $deployer->output->writeln($exception->getTraceAsString()); - - if ($exception instanceof GracefulShutdownException) { - throw $exception; - } - - // Check if we have tasks to execute on failure - if ($deployer['fail']->has($task)) { - $taskName = $deployer['fail']->get($task); - $tasks = $deployer->scriptManager->getTasks($taskName); + pcntl_signal(SIGINT, function () { + $this->log->warning("Received signal SIGINT, running fail jobs"); + // We don't have to do anything here. Underlying processes will receive the SIGINT signal as well + // and that will cause the $exitCode below to be 255, which will cause the fail tasks to be run. + }); - $executor->run($tasks, $hosts); - } - throw $exception; + /** + * Set the env variable to tell deployer to deploy the hosts sequentially instead of parallel. + * @see \Deployer\Executor\Master::runTask() + */ + putenv('DEPLOYER_LOCAL_WORKER=true'); + $exitCode = $executor->run($tasks, $hosts); + + if ($exitCode === 0) { + $this->deployedHostnames = array_map(fn(Host $host) => $host->getHostname(), $hosts); + $this->deployedStage = $stage; + return 0; } - } - private function tryGetConfiguration(): Configuration - { - $file = $this->input->getOption('file'); - if (!$file) { - $file = 'deploy.php'; + if ($exitCode === GracefulShutdownException::EXIT_CODE) { + return 1; } - if (!is_readable($file)) { - throw new \RuntimeException(sprintf('No %s file found in project root %s', $file, getcwd())); - } + // Check if we have tasks to execute on failure + if ($deployer['fail']->has($task)) { + $taskName = $deployer['fail']->get($task); + $tasks = $deployer->scriptManager->getTasks($taskName); - $configuration = \call_user_func(function () use ($file) { - return require $file; - }); - - if (!$configuration instanceof Configuration) { - throw new \RuntimeException( - sprintf('%s/deploy.php did not return object of type %s', getcwd(), Configuration::class) - ); + $executor->run($tasks, $hosts); } - return $configuration; - } + $this->brancherHypernodeManager->cancel(...$this->brancherHypernodesRegistered); - /** - * @throws GracefulShutdownException - * @throws Throwable - * @throws Exception - */ - private function tryComposerInstall(Deployer $deployer): void - { - /** @psalm-suppress InvalidArgument deployer will have proper typing in 7.x */ - $host = localhost('composer-prepare'); - $host->set('labels', ['stage' => 'composer-prepare']); - $host->set('bin/php', 'php'); - - task('composer-prepare:install', function () { - run('composer install --ignore-platform-reqs --optimize-autoloader --no-dev'); - }); - - task('composer-prepare', [ - 'deploy:vendors:auth', - 'composer-prepare:install', - ]); - - $this->runStage($deployer, 'composer-prepare', 'composer-prepare'); + return $exitCode; } - /** - * Initialize autoloader of the application being deployed - */ - private function initializeAppAutoloader(): void + public function getDeploymentReport() { - /** @psalm-suppress UndefinedConstant */ - if (file_exists(WORKING_DIR . '/vendor/autoload.php')) { - require_once WORKING_DIR . '/vendor/autoload.php'; - } + return new Report\Report( + $this->deployedStage, + $this->deployedHostnames, + $this->brancherHypernodesRegistered + ); } } diff --git a/src/Deployer/Task/After/AfterTaskGlobal.php b/src/Deployer/Task/After/AfterTaskGlobal.php index b000649..3655425 100644 --- a/src/Deployer/Task/After/AfterTaskGlobal.php +++ b/src/Deployer/Task/After/AfterTaskGlobal.php @@ -2,10 +2,9 @@ namespace Hypernode\Deploy\Deployer\Task\After; -use Hypernode\Deploy\Deployer\TaskBuilder; use Hypernode\Deploy\Deployer\Task\TaskBase; +use Hypernode\Deploy\Deployer\TaskBuilder; use Hypernode\DeployConfiguration\Configuration; -use Hypernode\DeployConfiguration\ServerRole; use function count; use function Deployer\task; @@ -32,9 +31,6 @@ public function configure(Configuration $config): void }; } - $role = ServerRole::APPLICATION; - task('deploy:after', $tasks) - ->once() - ->select("roles=$role"); + task('deploy:after', $tasks)->once(); } } diff --git a/src/Deployer/Task/After/CachetoolTask.php b/src/Deployer/Task/After/CachetoolTask.php index c3e33c8..c522224 100644 --- a/src/Deployer/Task/After/CachetoolTask.php +++ b/src/Deployer/Task/After/CachetoolTask.php @@ -3,8 +3,6 @@ namespace Hypernode\Deploy\Deployer\Task\After; use Hypernode\Deploy\Deployer\Task\TaskBase; -use Hypernode\DeployConfiguration\ServerRole; -use Hypernode\Deploy\Deployer\Task\TaskInterface; use Hypernode\DeployConfiguration\Configuration; use function Deployer\after; @@ -21,6 +19,7 @@ class CachetoolTask extends TaskBase /** * @var string[] * + * CacheTool 9.x/10.x works with PHP >=8.1 * CacheTool 8.x works with PHP >=8.0 * CacheTool 7.x works with PHP >=7.3 * CacheTool 6.x works with PHP >=7.3 @@ -28,7 +27,8 @@ class CachetoolTask extends TaskBase * CacheTool 4.x works with PHP >=7.1 */ private $versionBinaryMapping = [ - 8 => 'https://github.com/gordalina/cachetool/releases/download/8.4.0/cachetool.phar', + 10 => 'https://github.com/gordalina/cachetool/releases/download/10.0.0/cachetool.phar', + 8 => 'https://github.com/gordalina/cachetool/releases/download/8.6.1/cachetool.phar', 7 => 'https://github.com/gordalina/cachetool/releases/download/7.1.0/cachetool.phar', 6 => 'https://github.com/gordalina/cachetool/releases/download/6.6.0/cachetool.phar', 5 => 'https://github.com/gordalina/cachetool/releases/download/5.1.3/cachetool.phar', @@ -37,7 +37,6 @@ class CachetoolTask extends TaskBase public function register(): void { - after('deploy:symlink', 'cachetool:clear:opcache'); after('cachetool:clear:opcache', 'cachetool:cleanup'); } @@ -54,10 +53,10 @@ public function configure(Configuration $config): void $cachetoolBinary = get('cachetool_binary'); within('{{release_path}}', function () { - run('curl -L -o cachetool.phar ' . $this->getCachetoolUrl()); + $phpVersion = $this->getPhpVersion(); $cachetoolBinary = '{{release_path}}/cachetool.phar'; - - writeln(sprintf("Downloaded cachetool %s for PHP %d", $cachetoolBinary, $this->getPhpVersion())); + run('curl -L -o cachetool.phar ' . $this->getCachetoolUrl($phpVersion)); + writeln(sprintf("Downloaded cachetool %s for PHP %s", $cachetoolBinary, $phpVersion)); return $cachetoolBinary; }); return $cachetoolBinary; @@ -98,40 +97,43 @@ public function configure(Configuration $config): void run("cd {{release_path}} && {{bin/php}} {{bin/cachetool}} apcu:cache:clear {{cachetool_options}}"); }); - - $role = ServerRole::APPLICATION; - task('cachetool:clear:opcache') - ->select("roles=$role"); + task('cachetool:clear:opcache'); task('cachetool:cleanup', function () { run('cd {{deploy_path}} && rm -f current/{{bin/cachetool}}'); - })->select("roles=$role"); + }); } - protected function getPhpVersion(): float + protected function getPhpVersion(): string { - return (float) run('{{bin/php}} -r "echo PHP_VERSION . \" - \" . PHP_VERSION_ID;"'); + return run('{{bin/php}} -r "echo PHP_VERSION;"'); } - public function getCachetoolUrl(): string + public function getCachetoolUrl(?string $phpVersion = null): string { - $phpVersion = $this->getPhpVersion(); - if ($phpVersion >= 8.0) { + $phpVersion = $phpVersion ?? $this->getPhpVersion(); + + if (version_compare($phpVersion, '8.1.0', '>=')) { + return $this->versionBinaryMapping[10]; + } + + if (version_compare($phpVersion, '8.0.0', '>=')) { return $this->versionBinaryMapping[8]; } - if ($phpVersion >= 7.3) { - return $this->versionBinaryMapping[6]; + if (version_compare($phpVersion, '7.3.0', '>=')) { + return $this->versionBinaryMapping[7]; } - if ($phpVersion >= 7.2) { + if (version_compare($phpVersion, '7.2.0', '>=')) { return $this->versionBinaryMapping[5]; } - if ($phpVersion >= 7.1) { + if (version_compare($phpVersion, '7.1.0', '>=')) { return $this->versionBinaryMapping[4]; } - return $this->versionBinaryMapping[8]; + // Default to latest for unknown/newer PHP versions + return $this->versionBinaryMapping[10]; } } diff --git a/src/Deployer/Task/After/SlackTask.php b/src/Deployer/Task/After/SlackTask.php index 1aab66d..f3d62ef 100644 --- a/src/Deployer/Task/After/SlackTask.php +++ b/src/Deployer/Task/After/SlackTask.php @@ -35,7 +35,7 @@ public function supports(TaskConfigurationInterface $config): bool */ public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Task { - $this->recipeLoader->load('slack.php'); + $this->recipeLoader->load('../contrib/slack.php'); set('slack_webhook', $config->getWebHook()); set('slack_text', '{{release_message}}'); diff --git a/src/Deployer/Task/Build/ComposerAuthTaskGlobal.php b/src/Deployer/Task/Build/ComposerAuthTaskGlobal.php index 0b3e15f..837e82b 100644 --- a/src/Deployer/Task/Build/ComposerAuthTaskGlobal.php +++ b/src/Deployer/Task/Build/ComposerAuthTaskGlobal.php @@ -12,13 +12,29 @@ class ComposerAuthTaskGlobal extends TaskBase { + public const ENV_COMPOSER_AUTH = 'DEPLOY_COMPOSER_AUTH'; + + public function getAuthContent(): string + { + // Env var fetched to ensure key value is set + $rawContents = \Hypernode\DeployConfiguration\getenv(self::ENV_COMPOSER_AUTH); + $auth = base64_decode($rawContents, true); + if ($auth === false) { + // If not base64 encoded, we try to parse it as json. If that + // fails as well, it will raise. Otherwise, we'll just set + // $auth to the raw content. + /** @psalm-suppress UnusedFunctionCall We only call json_decode for validation */ + json_decode($rawContents, true, flags: JSON_THROW_ON_ERROR); + $auth = $rawContents; + } + return $auth; + } + public function configure(Configuration $config): void { task('deploy:vendors:auth', function () { - if (test('[ ! -f auth.json ]') && \getenv('DEPLOY_COMPOSER_AUTH')) { - // Env var fetched to ensure key value is set - $auth = \Hypernode\DeployConfiguration\getenv('DEPLOY_COMPOSER_AUTH'); - $auth = base64_decode($auth); + if (test('[ ! -f auth.json ]') && \getenv(self::ENV_COMPOSER_AUTH)) { + $auth = $this->getAuthContent(); run(sprintf('echo %s > auth.json', escapeshellarg($auth))); } })->select("stage=build"); diff --git a/src/Deployer/Task/Build/HighPerformanceStaticDeployTask.php b/src/Deployer/Task/Build/HighPerformanceStaticDeployTask.php new file mode 100644 index 0000000..bd2a7ff --- /dev/null +++ b/src/Deployer/Task/Build/HighPerformanceStaticDeployTask.php @@ -0,0 +1,59 @@ +isEnabled($config)) { + return; + } + + task('magento:deploy:assets', function () { + $themes = get('magento_themes', []); + $themeArgs = $this->buildThemeArgs($themes); + $locales = get('static_content_locales', 'en_US'); + $contentVersion = get('content_version', time()); + + within('{{release_or_current_path}}', function () use ($themeArgs, $locales, $contentVersion) { + run(self::BINARY_PATH . " --force --area=frontend --area=adminhtml $themeArgs --content-version=$contentVersion --verbose $locales"); + }); + })->select('stage=build'); + } + + public function isEnabled(Configuration $config): bool + { + $variables = $config->getVariables(); + $buildVariables = $config->getVariables('build'); + + return $variables['high_performance_static_deploy'] + ?? $buildVariables['high_performance_static_deploy'] + ?? false; + } + + /** + * @param array $themes + */ + public function buildThemeArgs(array $themes): string + { + return implode(' ', array_map(fn($t) => "--theme=$t", array_keys($themes))); + } +} diff --git a/src/Deployer/Task/Common/DefaultsTaskGlobal.php b/src/Deployer/Task/Common/DefaultsTaskGlobal.php index 6edd1a4..da36b27 100644 --- a/src/Deployer/Task/Common/DefaultsTaskGlobal.php +++ b/src/Deployer/Task/Common/DefaultsTaskGlobal.php @@ -8,6 +8,7 @@ use Hypernode\Deploy\Stdlib\CpuCoreInfo; use Hypernode\Deploy\Stdlib\ReleaseInfo; use Hypernode\DeployConfiguration\Configuration; +use Hypernode\Deploy\Stdlib\TargetFinder; use function Deployer\set; @@ -23,10 +24,16 @@ class DefaultsTaskGlobal extends TaskBase */ private $releaseInfo; - public function __construct(CpuCoreInfo $cpuInfo, ReleaseInfo $releaseInfo) + /** + * @var TargetFinder + */ + private $targetFinder; + + public function __construct(CpuCoreInfo $cpuInfo, ReleaseInfo $releaseInfo, TargetFinder $targetFinder) { $this->cpuInfo = $cpuInfo; $this->releaseInfo = $releaseInfo; + $this->targetFinder = $targetFinder; } public function configure(Configuration $config): void @@ -41,11 +48,23 @@ public function configure(Configuration $config): void return $this->releaseInfo->getMessage(); }); + set('target', function () { + return $this->targetFinder->getTarget(); + }); + set('commit_sha', function () { - return $this->releaseInfo->getCommitSha(); + try { + return $this->releaseInfo->getCommitSha(); + } catch (\Throwable $e) { + return ''; + } }); - set('configured/bin/php', 'php'); + if (str_starts_with($config->getPhpVersion(), 'php')) { + set('configured/bin/php', $config->getPhpVersion()); + } else { + set('configured/bin/php', 'php' . $config->getPhpVersion()); + } set('configured/public_folder', $config->getPublicFolder()); } } diff --git a/src/Deployer/Task/Common/PrepareSshTaskGlobal.php b/src/Deployer/Task/Common/PrepareSshTaskGlobal.php index 9d2e954..4ec74a8 100644 --- a/src/Deployer/Task/Common/PrepareSshTaskGlobal.php +++ b/src/Deployer/Task/Common/PrepareSshTaskGlobal.php @@ -4,9 +4,9 @@ namespace Hypernode\Deploy\Deployer\Task\Common; +use Deployer\Exception\RunException; use Hypernode\Deploy\Deployer\Task\TaskBase; use Hypernode\DeployConfiguration\Configuration; -use Symfony\Component\Process\Exception\ProcessFailedException; use function Deployer\get; use function Deployer\runLocally; @@ -19,28 +19,44 @@ class PrepareSshTaskGlobal extends TaskBase { private const BITBUCKET_KEY_PATH = '/opt/atlassian/pipelines/agent/ssh/id_rsa'; + private const KEY_PATHS = [ + self::BITBUCKET_KEY_PATH, + '~/.ssh/id_rsa', + '~/.ssh/id_ed25519' + ]; + public function configure(Configuration $config): void { set('ssh_key_file', function () { - if (testLocally('[ -f ' . self::BITBUCKET_KEY_PATH . ' ]')) { - return self::BITBUCKET_KEY_PATH; + foreach (self::KEY_PATHS as $path) { + if (testLocally("[ -f $path ]")) { + return $path; + } } - return '~/.ssh/id_rsa'; + + $home = getenv('HOME'); + if (!is_dir($home . '/.ssh')) { + mkdir($home . '/.ssh', 0700, true); + } + return $home . '/.ssh/id_rsa'; // Fallback }); task('prepare:ssh', function () { $this->configureKey(); - if (testLocally('ssh-add -l | grep -q "no identities"')) { + $agentHasNoIdentities = testLocally('ssh-add -l | grep -q "no identities"'); + $sshKeyFileExists = testLocally('[ -f {{ssh_key_file}} ]'); + + if ($agentHasNoIdentities && $sshKeyFileExists) { try { runLocally('ssh-add -k {{ssh_key_file}}'); - } catch (ProcessFailedException $e) { + } catch (RunException $e) { writeln('Failed to add key to ssh agent.'); writeln('Trying key {{ssh_key_file}}'); try { $keyMd5 = runLocally('md5sum {{ssh_key_file}}'); writeln("With MD5 $keyMd5"); - } catch (ProcessFailedException $e) { + } catch (RunException $e) { writeln('Failed to get keyfile MD5 ' . $e); } throw $e; @@ -70,7 +86,7 @@ private function configureKey(): void return; } - if (strpos($key, 'BEGIN RSA PRIVATE KEY') === false) { + if (!preg_match('/BEGIN (OPENSSH|RSA) PRIVATE KEY/', $key)) { runLocally('echo "$SSH_PRIVATE_KEY" | base64 -d > {{ssh_key_file}}'); } else { runLocally('echo "$SSH_PRIVATE_KEY" > {{ssh_key_file}}'); diff --git a/src/Deployer/Task/Deploy/CopyTask.php b/src/Deployer/Task/Deploy/CopyTask.php index f31a98c..99e6095 100644 --- a/src/Deployer/Task/Deploy/CopyTask.php +++ b/src/Deployer/Task/Deploy/CopyTask.php @@ -4,7 +4,6 @@ use Hypernode\Deploy\Deployer\Task\TaskBase; use Hypernode\DeployConfiguration\Configuration; -use Hypernode\DeployConfiguration\ServerRole; use function Deployer\run; use function Deployer\task; @@ -14,7 +13,6 @@ class CopyTask extends TaskBase { public function configure(Configuration $config): void { - $role = ServerRole::APPLICATION; task('deploy:copy:code', function () use ($config) { $packageFilepath = $config->getBuildArchiveFile(); $packageFilename = pathinfo($packageFilepath, PATHINFO_BASENAME); @@ -22,11 +20,11 @@ public function configure(Configuration $config): void upload($packageFilepath, '{{release_path}}'); run('cd {{release_path}} && tar -xf ' . $packageFilename); run('cd {{release_path}} && rm -f ' . $packageFilename); - })->select("roles=$role"); + }); task('deploy:copy', [ 'deploy:copy:code', 'deploy:shared', - ])->select("roles=$role"); + ]); } } diff --git a/src/Deployer/Task/Deploy/DeployTask.php b/src/Deployer/Task/Deploy/DeployTask.php index 06959f8..9fa040e 100644 --- a/src/Deployer/Task/Deploy/DeployTask.php +++ b/src/Deployer/Task/Deploy/DeployTask.php @@ -2,9 +2,8 @@ namespace Hypernode\Deploy\Deployer\Task\Deploy; -use Hypernode\Deploy\Deployer\Task\TaskBase; -use Hypernode\DeployConfiguration\ServerRole; use Hypernode\Deploy\Deployer\RecipeLoader; +use Hypernode\Deploy\Deployer\Task\TaskBase; use Hypernode\DeployConfiguration\Configuration; use function Deployer\fail; @@ -25,13 +24,12 @@ public function __construct(RecipeLoader $loader) public function configure(Configuration $config): void { $this->loader->load('deploy/info.php'); - $role = ServerRole::APPLICATION; task('deploy', [ 'deploy:upload', 'deploy:link', 'deploy:finalize', - ])->select("roles=$role"); + ]); fail('deploy', 'deploy:failed'); } diff --git a/src/Deployer/Task/Deploy/DeployTaskGlobal.php b/src/Deployer/Task/Deploy/DeployTaskGlobal.php index b3d6a9b..d68dd9e 100644 --- a/src/Deployer/Task/Deploy/DeployTaskGlobal.php +++ b/src/Deployer/Task/Deploy/DeployTaskGlobal.php @@ -4,7 +4,6 @@ use Hypernode\Deploy\Deployer\Task\TaskBase; use Hypernode\DeployConfiguration\Configuration; -use Hypernode\DeployConfiguration\ServerRole; use function Deployer\task; use function Hypernode\Deploy\Deployer\noop; @@ -14,7 +13,6 @@ class DeployTaskGlobal extends TaskBase public function configure(Configuration $config): void { $tasks = $config->getDeployTasks(); - $role = ServerRole::APPLICATION; if (count($tasks)) { task('deploy:deploy', $tasks); @@ -22,6 +20,6 @@ public function configure(Configuration $config): void task('deploy:deploy', noop()); } - task('deploy:deploy', $tasks)->select("roles=$role"); + task('deploy:deploy', $tasks); } } diff --git a/src/Deployer/Task/Deploy/FinalizeTask.php b/src/Deployer/Task/Deploy/FinalizeTask.php index c9628ea..eafec6c 100644 --- a/src/Deployer/Task/Deploy/FinalizeTask.php +++ b/src/Deployer/Task/Deploy/FinalizeTask.php @@ -2,11 +2,11 @@ namespace Hypernode\Deploy\Deployer\Task\Deploy; -use Hypernode\Deploy\Deployer\Task\TaskBase; -use Hypernode\DeployConfiguration\ServerRole; use Hypernode\Deploy\Deployer\RecipeLoader; +use Hypernode\Deploy\Deployer\Task\TaskBase; use Hypernode\DeployConfiguration\Configuration; +use function Deployer\after; use function Deployer\fail; use function Deployer\task; @@ -25,15 +25,15 @@ public function __construct(RecipeLoader $loader) public function configure(Configuration $config): void { $this->loader->load('deploy/info.php'); - $role = ServerRole::APPLICATION; task('deploy:finalize', [ 'deploy:after', 'deploy:unlock', 'deploy:cleanup', 'deploy:success', - ])->select("roles=$role"); + ]); fail('deploy', 'deploy:failed'); + after('deploy:failed', 'deploy:unlock'); } } diff --git a/src/Deployer/Task/Deploy/LinkTask.php b/src/Deployer/Task/Deploy/LinkTask.php index acd6206..bdd595b 100644 --- a/src/Deployer/Task/Deploy/LinkTask.php +++ b/src/Deployer/Task/Deploy/LinkTask.php @@ -4,7 +4,6 @@ use Hypernode\Deploy\Deployer\Task\TaskBase; use Hypernode\DeployConfiguration\Configuration; -use Hypernode\DeployConfiguration\ServerRole; use function Deployer\run; use function Deployer\task; @@ -14,11 +13,10 @@ class LinkTask extends TaskBase { public function configure(Configuration $config): void { - $role = ServerRole::APPLICATION; task('deploy:link', [ 'deploy:symlink', 'deploy:public_link', - ])->select("roles=$role"); + ]); // Symlink public_html folder task('deploy:public_link', function () { @@ -28,10 +26,11 @@ public function configure(Configuration $config): void return; } else { run('rmdir /data/web/public'); + run('ln -s {{current_path}}/{{public_folder}} /data/web/public'); } + } else { + run('ln -s {{current_path}}/{{public_folder}} /data/web/public'); } - - run('ln -s {{app_current_path}}/{{public_folder}} /data/web/public'); - })->select("roles=$role"); + }); } } diff --git a/src/Deployer/Task/Deploy/PrepareTask.php b/src/Deployer/Task/Deploy/PrepareTask.php index 63e797c..6a0d19b 100644 --- a/src/Deployer/Task/Deploy/PrepareTask.php +++ b/src/Deployer/Task/Deploy/PrepareTask.php @@ -23,9 +23,5 @@ public function configure(Configuration $config): void 'deploy:shared', 'deploy:writable', ]); - task('deploy:prepare_release', [ - 'deploy:prepare', - 'deploy:release', - ])->select("roles=$role"); } } diff --git a/src/Deployer/Task/Deploy/UploadTask.php b/src/Deployer/Task/Deploy/UploadTask.php index 05eb608..10a57d7 100644 --- a/src/Deployer/Task/Deploy/UploadTask.php +++ b/src/Deployer/Task/Deploy/UploadTask.php @@ -2,9 +2,8 @@ namespace Hypernode\Deploy\Deployer\Task\Deploy; -use Hypernode\Deploy\Deployer\Task\TaskBase; -use Hypernode\DeployConfiguration\ServerRole; use Hypernode\Deploy\Deployer\RecipeLoader; +use Hypernode\Deploy\Deployer\Task\TaskBase; use Hypernode\DeployConfiguration\Configuration; use function Deployer\fail; @@ -25,15 +24,14 @@ public function __construct(RecipeLoader $loader) public function configure(Configuration $config): void { $this->loader->load('deploy/info.php'); - $role = ServerRole::APPLICATION; task('deploy:upload', [ - 'deploy:info', 'prepare:ssh', - 'deploy:prepare_release', + 'deploy:info', + 'deploy:prepare', 'deploy:copy', 'deploy:deploy', - ])->select("roles=$role"); + ]); fail('deploy', 'deploy:failed'); } diff --git a/src/Deployer/Task/PlatformConfiguration/CronSyncTask.php b/src/Deployer/Task/PlatformConfiguration/CronSyncTask.php index 0017052..5c608fb 100644 --- a/src/Deployer/Task/PlatformConfiguration/CronSyncTask.php +++ b/src/Deployer/Task/PlatformConfiguration/CronSyncTask.php @@ -35,7 +35,7 @@ public function getCurrentCrontab(): string public function setCrontab(string $newCrontab): void { - run('echo "' . $newCrontab . '" | crontab -'); + run("echo $'" . str_replace('\'', '\\\'', $newCrontab) . "' | crontab -"); } public function replaceExistingCronBlocks(string $newCronBlock): string @@ -51,7 +51,7 @@ public function replaceExistingCronBlocks(string $newCronBlock): string return $newCrontab; } else { writeln("Replacing cron block for {{domain}}"); - return preg_replace('/^' . $beginOld . '$.*^' . $endOld . '$/ms', $newCronBlock, $currentCrontab); + return (string)preg_replace('/^' . $beginOld . '$.*^' . $endOld . '$/ms', $newCronBlock, $currentCrontab); } } diff --git a/src/Deployer/Task/PlatformConfiguration/CronTask.php b/src/Deployer/Task/PlatformConfiguration/CronTask.php index a894608..f75af91 100644 --- a/src/Deployer/Task/PlatformConfiguration/CronTask.php +++ b/src/Deployer/Task/PlatformConfiguration/CronTask.php @@ -2,19 +2,15 @@ namespace Hypernode\Deploy\Deployer\Task\PlatformConfiguration; -use Hypernode\Deploy\Deployer\Task\IncrementedTaskTrait; use Deployer\Task\Task; use Hypernode\Deploy\Deployer\Task\ConfigurableTaskInterface; +use Hypernode\Deploy\Deployer\Task\IncrementedTaskTrait; use Hypernode\Deploy\Deployer\Task\TaskBase; -use Hypernode\DeployConfiguration\Configuration; use Hypernode\DeployConfiguration\PlatformConfiguration\CronConfiguration; use Hypernode\DeployConfiguration\TaskConfigurationInterface; use function Deployer\before; -use function Deployer\get; -use function Deployer\set; use function Deployer\task; -use function Deployer\writeln; class CronTask extends TaskBase implements ConfigurableTaskInterface { @@ -27,14 +23,14 @@ protected function getIncrementalNamePrefix(): string public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Task { - task('deploy:cron', [ + $task = task('deploy:cron', [ 'deploy:cron:render', 'deploy:cron:sync', ]); before('deploy:symlink', 'deploy:cron'); - return null; + return $task; } public function supports(TaskConfigurationInterface $config): bool diff --git a/src/Deployer/Task/PlatformConfiguration/HypernodeSettingTask.php b/src/Deployer/Task/PlatformConfiguration/HypernodeSettingTask.php index 97c745e..3f6775d 100644 --- a/src/Deployer/Task/PlatformConfiguration/HypernodeSettingTask.php +++ b/src/Deployer/Task/PlatformConfiguration/HypernodeSettingTask.php @@ -33,9 +33,9 @@ public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Ta Assert::isInstanceOf($config, HypernodeSettingConfiguration::class); $attribute = $config->getAttribute(); $value = $config->getValue(); - $taskName = "deploy:hypernode:setting:{$attribute}"; + $taskName = "deploy:hypernode:setting:$attribute"; $task = task($taskName, function () use ($attribute, $value) { - run("hypernode-systemctl settings {$attribute} {$value}"); + run("yes | hypernode-systemctl settings $attribute $value --block"); }); after('deploy:setup', $taskName); return $task; diff --git a/src/Deployer/Task/PlatformConfiguration/NginxManageVHostTask.php b/src/Deployer/Task/PlatformConfiguration/NginxManageVHostTask.php index 33a766e..1091126 100644 --- a/src/Deployer/Task/PlatformConfiguration/NginxManageVHostTask.php +++ b/src/Deployer/Task/PlatformConfiguration/NginxManageVHostTask.php @@ -2,11 +2,10 @@ namespace Hypernode\Deploy\Deployer\Task\PlatformConfiguration; -use Hypernode\Deploy\Deployer\Task\IncrementedTaskTrait; use Deployer\Task\Task; use Hypernode\Deploy\Deployer\Task\ConfigurableTaskInterface; +use Hypernode\Deploy\Deployer\Task\IncrementedTaskTrait; use Hypernode\Deploy\Deployer\Task\TaskBase; -use Hypernode\DeployConfiguration\Configuration; use Hypernode\DeployConfiguration\PlatformConfiguration\NginxConfiguration; use Hypernode\DeployConfiguration\TaskConfigurationInterface; @@ -29,10 +28,10 @@ public function supports(TaskConfigurationInterface $config): bool public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Task { - task('deploy:nginx:manage_vhost', function () { - run('hypernode-manage-vhosts {{domain}} --webroot {{app_current_path}}/{{public_folder}} --no'); + $task = task('deploy:nginx:manage_vhost', function () { + run('hypernode-manage-vhosts {{domain}} --webroot {{current_path}}/{{public_folder}} --no'); }); - return null; + return $task; } } diff --git a/src/Deployer/Task/PlatformConfiguration/NginxSyncTask.php b/src/Deployer/Task/PlatformConfiguration/NginxSyncTask.php index 7349341..3594306 100644 --- a/src/Deployer/Task/PlatformConfiguration/NginxSyncTask.php +++ b/src/Deployer/Task/PlatformConfiguration/NginxSyncTask.php @@ -59,6 +59,7 @@ public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Ta '--delete', ]; $args = implode(' ', array_map('escapeshellarg', $args)); + run("mkdir -p {{nginx_release_path}}"); run("rsync {$args} {{nginx/config_path}}/ {{nginx_release_path}}/"); run("ln -sf {{nginx_current_path}} /data/web/nginx/{{domain}}"); }); diff --git a/src/Deployer/Task/PlatformConfiguration/NginxTask.php b/src/Deployer/Task/PlatformConfiguration/NginxTask.php index 92c903e..1280130 100644 --- a/src/Deployer/Task/PlatformConfiguration/NginxTask.php +++ b/src/Deployer/Task/PlatformConfiguration/NginxTask.php @@ -2,9 +2,9 @@ namespace Hypernode\Deploy\Deployer\Task\PlatformConfiguration; -use Hypernode\Deploy\Deployer\Task\IncrementedTaskTrait; use Deployer\Task\Task; use Hypernode\Deploy\Deployer\Task\ConfigurableTaskInterface; +use Hypernode\Deploy\Deployer\Task\IncrementedTaskTrait; use Hypernode\Deploy\Deployer\Task\TaskBase; use Hypernode\DeployConfiguration\PlatformConfiguration\NginxConfiguration; use Hypernode\DeployConfiguration\TaskConfigurationInterface; @@ -35,19 +35,20 @@ public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Ta return '/tmp/nginx-config-' . get('domain'); }); - task('deploy:nginx', [ + $task = task('deploy:nginx', [ 'deploy:nginx:prepare', - 'deploy:nginx:manage_vhost', 'deploy:nginx:upload', 'deploy:nginx:sync', 'deploy:nginx:cleanup', ]); before('deploy:symlink', 'deploy:nginx'); + after('deploy:symlink', 'deploy:nginx:manage_vhost'); + foreach ($this->getRegisteredTasks() as $taskName) { after('deploy:nginx:prepare', $taskName); } - return null; + return $task; } } diff --git a/src/Deployer/Task/PlatformConfiguration/RedisEnableTask.php b/src/Deployer/Task/PlatformConfiguration/RedisEnableTask.php index 8d53bcc..4470e52 100644 --- a/src/Deployer/Task/PlatformConfiguration/RedisEnableTask.php +++ b/src/Deployer/Task/PlatformConfiguration/RedisEnableTask.php @@ -36,7 +36,7 @@ public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Ta { if ($config->useSupervisor()) { task(self::TASK_NAME, function () use ($config) { - run("hypernode-systemctl settings redis_version {$config->getVersion()} --block"); + run("echo yes | hypernode-systemctl settings redis_version {$config->getVersion()} --block"); run("hypernode-systemctl settings supervisor_enabled True --block"); $instance = $config->getPersistence() ? 'redis-persistent' : 'redis'; @@ -44,7 +44,7 @@ public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Ta }); } else { task(self::TASK_NAME, function () use ($config) { - run("hypernode-systemctl settings redis_version {$config->getVersion()} --block"); + run("echo yes | hypernode-systemctl settings redis_version {$config->getVersion()} --block"); if ($config->getPersistence()) { run("hypernode-systemctl settings redis_persistent_instance --value True --block"); } diff --git a/src/Deployer/Task/PlatformConfiguration/RedisTask.php b/src/Deployer/Task/PlatformConfiguration/RedisTask.php index 6d07126..e069179 100644 --- a/src/Deployer/Task/PlatformConfiguration/RedisTask.php +++ b/src/Deployer/Task/PlatformConfiguration/RedisTask.php @@ -2,9 +2,9 @@ namespace Hypernode\Deploy\Deployer\Task\PlatformConfiguration; -use Hypernode\Deploy\Deployer\Task\IncrementedTaskTrait; use Deployer\Task\Task; use Hypernode\Deploy\Deployer\Task\ConfigurableTaskInterface; +use Hypernode\Deploy\Deployer\Task\IncrementedTaskTrait; use Hypernode\Deploy\Deployer\Task\TaskBase; use Hypernode\DeployConfiguration\PlatformConfiguration\RedisConfiguration; use Hypernode\DeployConfiguration\TaskConfigurationInterface; @@ -30,11 +30,11 @@ public function supports(TaskConfigurationInterface $config): bool public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Task { - task('deploy:redis', [ + $task = task('deploy:redis', [ 'deploy:redis:enable', ]); before('deploy:symlink', self::TASK_NAME); - return null; + return $task; } } diff --git a/src/Deployer/Task/PlatformConfiguration/SupervisorTask.php b/src/Deployer/Task/PlatformConfiguration/SupervisorTask.php index cd154f6..f2aa255 100644 --- a/src/Deployer/Task/PlatformConfiguration/SupervisorTask.php +++ b/src/Deployer/Task/PlatformConfiguration/SupervisorTask.php @@ -2,9 +2,9 @@ namespace Hypernode\Deploy\Deployer\Task\PlatformConfiguration; -use Hypernode\Deploy\Deployer\Task\IncrementedTaskTrait; use Deployer\Task\Task; use Hypernode\Deploy\Deployer\Task\ConfigurableTaskInterface; +use Hypernode\Deploy\Deployer\Task\IncrementedTaskTrait; use Hypernode\Deploy\Deployer\Task\TaskBase; use Hypernode\DeployConfiguration\PlatformConfiguration\SupervisorConfiguration; use Hypernode\DeployConfiguration\TaskConfigurationInterface; @@ -35,7 +35,7 @@ public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Ta return '/tmp/supervisor-config-' . get('domain'); }); - task('deploy:supervisor', [ + $task = task('deploy:supervisor', [ 'deploy:supervisor:prepare', 'deploy:supervisor:upload', 'deploy:supervisor:sync', @@ -47,6 +47,6 @@ public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Ta after('deploy:supervisor:prepare', $taskName); } - return null; + return $task; } } diff --git a/src/Deployer/Task/PlatformConfiguration/SupervisorUploadTask.php b/src/Deployer/Task/PlatformConfiguration/SupervisorUploadTask.php index a227b5b..84f35d5 100644 --- a/src/Deployer/Task/PlatformConfiguration/SupervisorUploadTask.php +++ b/src/Deployer/Task/PlatformConfiguration/SupervisorUploadTask.php @@ -55,6 +55,7 @@ function () use ($config) { ]; upload($sourceDir . '/', '{{supervisor/config_path}}/'); $args = implode(' ', array_map('escapeshellarg', $args)); + run("mkdir -p {{supervisor_release_path}}"); run("rsync {$args} {{supervisor/config_path}}/ {{supervisor_release_path}}/"); } ); diff --git a/src/Deployer/Task/PlatformConfiguration/VarnishActivateTask.php b/src/Deployer/Task/PlatformConfiguration/VarnishActivateTask.php index 5b34666..7c47989 100644 --- a/src/Deployer/Task/PlatformConfiguration/VarnishActivateTask.php +++ b/src/Deployer/Task/PlatformConfiguration/VarnishActivateTask.php @@ -44,7 +44,7 @@ public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Ta }); task(self::TASK_NAME, function () { - run('{{varnishadm_path}} vcl.use {{domain}}.{{release_name}}_varnish'); + run('{{varnishadm_path}} vcl.use {{varnish_vcl_name}}'); }); after('deploy:symlink', self::TASK_NAME); diff --git a/src/Deployer/Task/PlatformConfiguration/VarnishEnableTask.php b/src/Deployer/Task/PlatformConfiguration/VarnishEnableTask.php index 9d452f1..1249bfb 100644 --- a/src/Deployer/Task/PlatformConfiguration/VarnishEnableTask.php +++ b/src/Deployer/Task/PlatformConfiguration/VarnishEnableTask.php @@ -36,7 +36,7 @@ public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Ta { if ($config->useSupervisor()) { task(self::TASK_NAME, function () use ($config) { - run("hypernode-systemctl settings varnish_version {$config->getVersion()} --block"); + run("echo yes | hypernode-systemctl settings varnish_version {$config->getVersion()} --block"); run("hypernode-systemctl settings supervisor_enabled True --block"); run("hypernode-manage-supervisor {{domain}}.{{release_name}} --service varnish --ram {$config->getMemory()}"); }); diff --git a/src/Deployer/Task/PlatformConfiguration/VarnishLoadTask.php b/src/Deployer/Task/PlatformConfiguration/VarnishLoadTask.php index d653e57..2a6d4b1 100644 --- a/src/Deployer/Task/PlatformConfiguration/VarnishLoadTask.php +++ b/src/Deployer/Task/PlatformConfiguration/VarnishLoadTask.php @@ -2,14 +2,15 @@ namespace Hypernode\Deploy\Deployer\Task\PlatformConfiguration; -use Hypernode\Deploy\Deployer\Task\IncrementedTaskTrait; use Deployer\Task\Task; use Hypernode\Deploy\Deployer\Task\ConfigurableTaskInterface; +use Hypernode\Deploy\Deployer\Task\IncrementedTaskTrait; use Hypernode\Deploy\Deployer\Task\TaskBase; use Hypernode\DeployConfiguration\PlatformConfiguration\VarnishConfiguration; use Hypernode\DeployConfiguration\TaskConfigurationInterface; use function Deployer\fail; +use function Deployer\get; use function Deployer\run; use function Deployer\set; use function Deployer\task; @@ -43,7 +44,14 @@ public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Ta }); task(self::TASK_NAME, function () { - run('{{varnishadm_path}} vcl.load {{domain}}.{{release_name}}_varnish {{varnish_release_path}}/varnish.vcl'); + $appName = get('domain'); + $appName = preg_replace('/[^a-zA-Z0-9-_]+/', '-', $appName); + $appName = trim($appName, '-'); + + $releaseName = get('release_name'); + $vclName = sprintf('varnish-%s-%s', $appName, $releaseName); + set('varnish_vcl_name', $vclName); + run('{{varnishadm_path}} vcl.load {{varnish_vcl_name}} {{varnish_release_path}}/varnish.vcl'); }); fail(self::TASK_NAME, 'deploy:varnish:cleanup'); diff --git a/src/Deployer/Task/PlatformConfiguration/VarnishTask.php b/src/Deployer/Task/PlatformConfiguration/VarnishTask.php index 15ea0e3..0a17ab1 100644 --- a/src/Deployer/Task/PlatformConfiguration/VarnishTask.php +++ b/src/Deployer/Task/PlatformConfiguration/VarnishTask.php @@ -2,9 +2,9 @@ namespace Hypernode\Deploy\Deployer\Task\PlatformConfiguration; -use Hypernode\Deploy\Deployer\Task\IncrementedTaskTrait; use Deployer\Task\Task; use Hypernode\Deploy\Deployer\Task\ConfigurableTaskInterface; +use Hypernode\Deploy\Deployer\Task\IncrementedTaskTrait; use Hypernode\Deploy\Deployer\Task\TaskBase; use Hypernode\DeployConfiguration\PlatformConfiguration\VarnishConfiguration; use Hypernode\DeployConfiguration\TaskConfigurationInterface; @@ -31,7 +31,7 @@ public function supports(TaskConfigurationInterface $config): bool public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Task { - task('deploy:varnish', [ + $task = task('deploy:varnish', [ 'deploy:varnish:enable', 'deploy:varnish:prepare', 'deploy:varnish:upload', @@ -46,6 +46,6 @@ public function configureWithTaskConfig(TaskConfigurationInterface $config): ?Ta after('deploy:varnish:prepare', $taskName); } - return null; + return $task; } } diff --git a/src/Deployer/TaskBuilder.php b/src/Deployer/TaskBuilder.php index 2b9b384..d97e767 100644 --- a/src/Deployer/TaskBuilder.php +++ b/src/Deployer/TaskBuilder.php @@ -8,8 +8,6 @@ use Deployer\Task\Task; use Hypernode\DeployConfiguration\Command\Command; use Hypernode\DeployConfiguration\Command\DeployCommand; -use Hypernode\DeployConfiguration\ServerRoleConfigurableInterface; -use Hypernode\DeployConfiguration\StageConfigurableInterface; use Hypernode\DeployConfiguration\TaskConfigurationInterface; use function Deployer\parse; @@ -54,15 +52,6 @@ private function build(Command $command, string $name): Task $this->runCommandWithin($command); }); - if ($command instanceof StageConfigurableInterface && $command->getStage()) { - $task->select("stage={$command->getStage()->getName()}"); - } - - if ($command instanceof ServerRoleConfigurableInterface && $command->getServerRoles()) { - $roles = implode("&", $command->getServerRoles()); - $task->select("roles=$roles"); - } - return $task; } diff --git a/src/DeployerLoader.php b/src/DeployerLoader.php new file mode 100644 index 0000000..062f645 --- /dev/null +++ b/src/DeployerLoader.php @@ -0,0 +1,44 @@ +deployer) { + return $this->deployer; + } + + $console = new Application(); + $this->deployer = new Deployer($console); + $this->deployer['output'] = new OutputWatcher($output); + $this->deployer['input'] = new ArrayInput( + [], + new InputDefinition([ + new InputOption('limit'), + new InputOption('profile'), + ]) + ); + if (getenv('GITHUB_WORKFLOW')) { + $this->deployer['pop'] = function ($c) { + return new GithubWorkflowPrinter($c['output']); + }; + } + + return $this->deployer; + } +} diff --git a/src/Di/ConsoleDefinition.php b/src/Di/ConsoleDefinition.php index 32a03c0..26b269d 100644 --- a/src/Di/ConsoleDefinition.php +++ b/src/Di/ConsoleDefinition.php @@ -1,11 +1,13 @@ output = $output; + } + + public function contentHasWorkflowCommand(string $content): bool + { + return (bool)preg_match(self::WORKFLOW_COMMAND_PATTERN, trim($content)); + } + + public function callback(Host $host, bool $forceOutput): callable + { + return function ($type, $buffer) use ($forceOutput, $host) { + if ( + $this->output->isVerbose() || $forceOutput || + ($type == Process::OUT && $this->contentHasWorkflowCommand($buffer)) + ) { + $this->printBuffer($type, $host, $buffer); + } + }; + } + + public function writeln(string $type, Host $host, string $line): void + { + if (empty($line)) { + return; + } + + if ($type == Process::OUT && $this->contentHasWorkflowCommand($line)) { + $this->output->writeln($line); + return; + } + + parent::writeln($type, $host, $line); + } +} diff --git a/src/Report/Report.php b/src/Report/Report.php new file mode 100644 index 0000000..bb12db6 --- /dev/null +++ b/src/Report/Report.php @@ -0,0 +1,86 @@ +stage = $stage; + $this->hostnames = $hostnames; + $this->brancherHypernodes = $brancherHypernodes; + $this->version = $version; + } + + public function getVersion(): string + { + return $this->version; + } + + public function getStage(): string + { + return $this->stage; + } + + /** + * @return string[] + */ + public function getHostnames(): array + { + return $this->hostnames; + } + + /** + * @return string[] + */ + public function getBrancherHypernodes(): array + { + return $this->brancherHypernodes; + } + + public function toArray(): array + { + return [ + 'version' => $this->version, + 'stage' => $this->stage, + 'hostnames' => $this->hostnames, + 'brancher_hypernodes' => $this->brancherHypernodes, + ]; + } + + public static function fromArray(array $data): Report + { + return new Report( + $data['stage'], + $data['hostnames'], + $data['brancher_hypernodes'], + $data['version'], + ); + } +} diff --git a/src/Report/ReportLoader.php b/src/Report/ReportLoader.php new file mode 100644 index 0000000..77c401e --- /dev/null +++ b/src/Report/ReportLoader.php @@ -0,0 +1,29 @@ +reportPath = $reportPath; + } + + public function loadReport(): ?Report + { + if (file_exists($this->reportPath)) { + $contents = file_get_contents($this->reportPath); + $data = json_decode($contents, true, flags: JSON_THROW_ON_ERROR); + Assert::isArray($data); + return Report::fromArray($data); + } + + return null; + } +} diff --git a/src/Report/ReportWriter.php b/src/Report/ReportWriter.php new file mode 100644 index 0000000..d4aca84 --- /dev/null +++ b/src/Report/ReportWriter.php @@ -0,0 +1,20 @@ +reportPath = $reportPath; + } + + public function write(Report $report): void + { + file_put_contents($this->reportPath, json_encode($report->toArray())); + } +} diff --git a/src/Stdlib/ReleaseInfo.php b/src/Stdlib/ReleaseInfo.php index 3be2fc6..1970d98 100644 --- a/src/Stdlib/ReleaseInfo.php +++ b/src/Stdlib/ReleaseInfo.php @@ -2,6 +2,7 @@ namespace Hypernode\Deploy\Stdlib; +use Deployer\Exception\RunException; use Hypernode\DeployConfiguration\Stage; use function Deployer\get; @@ -24,8 +25,8 @@ public function getCommitSha(): string public function getMessage(): string { $body = []; - $body[] = parse('Successful deployment to **{{stage}}**'); - $body[] = parse('Branch: `{{branch}}`'); + $body[] = parse('Successful deployment to *{{stage}}*'); + $body[] = parse('Branch: `{{target}}`'); $body[] = parse('User: `{{user}}`'); $body[] = parse('Commit: `{{commit_sha}}`'); @@ -39,7 +40,7 @@ public function getMessage(): string } $body[] = ''; - $body[] = '**Servers:**'; + $body[] = '*Servers:*'; foreach ($this->getServers() as $server) { $body[] = '- ' . $server; } @@ -52,7 +53,13 @@ public function getMessage(): string */ private function branchList(): array { - $gitLogOutput = runLocally('git log --merges -n 1'); + $gitLogOutput = ''; + + try { + $gitLogOutput = runLocally('git log --merges -n 1'); + } catch (RunException $e) { + return []; + } if (!preg_match(self::MERGE_PATTERN, $gitLogOutput, $matches)) { output()->write('No merge commit found'); diff --git a/src/Stdlib/TargetFinder.php b/src/Stdlib/TargetFinder.php new file mode 100644 index 0000000..5f19d50 --- /dev/null +++ b/src/Stdlib/TargetFinder.php @@ -0,0 +1,74 @@ +getBranchFromCI(); + if (!empty($branch)) { + return $branch; + } + + return get('branch', 'HEAD'); + } + + private function getBranchFromCI(): ?string + { + // Check GitHub Actions + if ($githubBranch = getenv('GITHUB_HEAD_REF')) { + return $githubBranch; + } + if ($githubBaseRef = getenv('GITHUB_REF')) { + return $this->parseGithubRef($githubBaseRef); + } + + // Check GitLab CI + if ($gitlabBranch = getenv('CI_COMMIT_REF_NAME')) { + return $gitlabBranch; + } + + // Check Bitbucket Pipelines + if ($bitbucketBranch = getenv('BITBUCKET_BRANCH')) { + return $bitbucketBranch; + } + + // Check Azure Pipelines + if ($azureBranch = getenv('BUILD_SOURCEBRANCH')) { + return $this->parseAzureBranch($azureBranch); + } + + return null; + } + + private function parseGithubRef(string $ref): ?string + { + // Extract branch or tag name from refs/heads/ or refs/tags/ + if (preg_match('#refs/heads/(.+)#', $ref, $matches)) { + return $matches[1]; + } + if (preg_match('#refs/tags/(.+)#', $ref, $matches)) { + return $matches[1]; + } + + return null; + } + + private function parseAzureBranch(string $branch): ?string + { + // Extract branch name from refs/heads/ + if (strpos($branch, 'refs/heads/') === 0) { + return substr($branch, strlen('refs/heads/')); + } + + return $branch; + } +} diff --git a/tests/Unit/Brancher/BrancherHypernodeManagerTest.php b/tests/Unit/Brancher/BrancherHypernodeManagerTest.php new file mode 100644 index 0000000..e226410 --- /dev/null +++ b/tests/Unit/Brancher/BrancherHypernodeManagerTest.php @@ -0,0 +1,349 @@ +logger = $this->createMock(LoggerInterface::class); + $this->hypernodeClient = $this->createMock(HypernodeClient::class); + $this->brancherApp = $this->createMock(BrancherApp::class); + $this->logbook = $this->createMock(Logbook::class); + $this->hypernodeClient->brancherApp = $this->brancherApp; + $this->hypernodeClient->logbook = $this->logbook; + $this->sshPoller = new TestSshPoller(); + $this->sshPoller->setMicrotime(1000.0); + + $this->manager = new BrancherHypernodeManager( + $this->logger, + $this->hypernodeClient, + $this->sshPoller + ); + } + + public function testSshFirstCheckSucceedsReturnsEarly(): void + { + $this->sshPoller->pollResults = [true, true, true]; + + $this->logbook->expects($this->never())->method('getList'); + + $this->manager->waitForAvailability('test-brancher', 1500, 6, 10); + + $this->assertSame(3, $this->sshPoller->pollCount); + } + + public function testSshFirstCheckFailsFallsBackToLogbook(): void + { + $this->sshPoller->pollResults = array_merge( + array_fill(0, 5, false), + array_fill(0, 6, true) + ); + + $flow = $this->createFlow('ensure_app', Flow::STATE_SUCCESS); + $this->logbook->expects($this->once()) + ->method('getList') + ->with('test-brancher') + ->willReturn([$flow]); + + $this->manager->waitForAvailability('test-brancher', 1500, 6, 10); + + $this->assertSame(11, $this->sshPoller->pollCount); + } + + public function testLogbookAllFlowsRevertedThrowsException(): void + { + $this->sshPoller->pollResults = array_fill(0, 5, false); + + $flow = $this->createFlow('ensure_app', Flow::STATE_REVERTED); + $this->logbook->expects($this->once()) + ->method('getList') + ->with('test-brancher') + ->willReturn([$flow]); + + $this->expectException(CreateBrancherHypernodeFailedException::class); + + $this->manager->waitForAvailability('test-brancher', 1500, 6, 10); + } + + public function testTimeoutDuringDeliveryThrowsException(): void + { + $this->sshPoller->pollResults = array_fill(0, 5, false); + $this->sshPoller->sleepTimeAdvance = 10; + + $flow = $this->createFlow('ensure_app', Flow::STATE_RUNNING); + $this->logbook->method('getList') + ->with('test-brancher') + ->willReturn([$flow]); + + $this->expectException(TimeoutException::class); + $this->expectExceptionMessage('Timed out waiting for brancher Hypernode test-brancher to be delivered'); + + $this->manager->waitForAvailability('test-brancher', 30, 6, 10); + } + + public function testTimeoutDuringReachabilityThrowsException(): void + { + $this->sshPoller->pollResults = array_fill(0, 100, false); + $this->sshPoller->sleepTimeAdvance = 10; + + $flow = $this->createFlow('ensure_app', Flow::STATE_SUCCESS); + $this->logbook->method('getList') + ->with('test-brancher') + ->willReturn([$flow]); + + $this->expectException(TimeoutException::class); + $this->expectExceptionMessage('Timed out waiting for brancher Hypernode test-brancher to become reachable'); + + $this->manager->waitForAvailability('test-brancher', 100, 6, 10); + } + + public function testLogbook404DuringAllowedWindowContinuesPolling(): void + { + $this->sshPoller->pollResults = array_merge( + array_fill(0, 5, false), + array_fill(0, 6, true) + ); + + $response = $this->createMock(ResponseInterface::class); + $response->method('getStatusCode')->willReturn(404); + $response->method('getBody')->willReturn('Not Found'); + $exception404 = new HypernodeApiClientException($response); + + $flow = $this->createFlow('ensure_app', Flow::STATE_SUCCESS); + $this->logbook->expects($this->exactly(2)) + ->method('getList') + ->with('test-brancher') + ->willReturnOnConsecutiveCalls( + $this->throwException($exception404), + [$flow] + ); + + $this->manager->waitForAvailability('test-brancher', 1500, 6, 10); + + $this->assertSame(11, $this->sshPoller->pollCount); + } + + public function testLogbookNon404ErrorPropagates(): void + { + $this->sshPoller->pollResults = array_fill(0, 5, false); + + $response = $this->createMock(ResponseInterface::class); + $response->method('getStatusCode')->willReturn(500); + $response->method('getBody')->willReturn('Internal Server Error'); + $exception500 = new HypernodeApiClientException($response); + + $this->logbook->expects($this->once()) + ->method('getList') + ->with('test-brancher') + ->willThrowException($exception500); + + $this->expectException(HypernodeApiClientException::class); + + $this->manager->waitForAvailability('test-brancher', 1500, 6, 10); + } + + public function testSshFirstCheckIntermittentFailuresResetCounter(): void + { + $this->sshPoller->pollResults = [true, true, false, true, true, true]; + + $this->logbook->expects($this->never())->method('getList'); + + $this->manager->waitForAvailability('test-brancher', 1500, 6, 10); + + $this->assertSame(6, $this->sshPoller->pollCount); + } + + public function testSshFirstCheckExhaustsMaxFailuresBeforeFallback(): void + { + $this->sshPoller->pollResults = array_merge( + array_fill(0, 5, false), + array_fill(0, 6, true) + ); + + $flow = $this->createFlow('ensure_app', Flow::STATE_SUCCESS); + $this->logbook->expects($this->once()) + ->method('getList') + ->willReturn([$flow]); + + $this->manager->waitForAvailability('test-brancher', 1500, 6, 10); + + $this->assertSame(11, $this->sshPoller->pollCount); + } + + public function testMultipleFlowsAllMustComplete(): void + { + $this->sshPoller->pollResults = array_merge( + array_fill(0, 5, false), + array_fill(0, 6, true) + ); + + $flowComplete = $this->createFlow('ensure_app', Flow::STATE_SUCCESS); + $flowRunning = $this->createFlow('ensure_copied_app', Flow::STATE_RUNNING); + $flowComplete2 = $this->createFlow('ensure_copied_app', Flow::STATE_SUCCESS); + + $this->logbook->expects($this->exactly(2)) + ->method('getList') + ->willReturnOnConsecutiveCalls( + [$flowComplete, $flowRunning], + [$flowComplete, $flowComplete2] + ); + + $this->manager->waitForAvailability('test-brancher', 1500, 6, 10); + + $this->assertSame(11, $this->sshPoller->pollCount); + } + + public function testEmptyLogbookContinuesPolling(): void + { + $this->sshPoller->pollResults = array_merge( + array_fill(0, 5, false), + array_fill(0, 6, true) + ); + + $flow = $this->createFlow('ensure_app', Flow::STATE_SUCCESS); + $this->logbook->expects($this->exactly(2)) + ->method('getList') + ->willReturnOnConsecutiveCalls([], [$flow]); + + $this->manager->waitForAvailability('test-brancher', 1500, 6, 10); + + $this->assertSame(11, $this->sshPoller->pollCount); + } + + public function testIrrelevantFlowsAreIgnored(): void + { + $this->sshPoller->pollResults = array_merge( + array_fill(0, 5, false), + array_fill(0, 6, true) + ); + + $irrelevantFlow = $this->createFlow('some_other_flow', Flow::STATE_RUNNING); + $relevantFlow = $this->createFlow('ensure_app', Flow::STATE_SUCCESS); + + $this->logbook->expects($this->exactly(2)) + ->method('getList') + ->willReturnOnConsecutiveCalls( + [$irrelevantFlow], + [$relevantFlow, $irrelevantFlow] + ); + + $this->manager->waitForAvailability('test-brancher', 1500, 6, 10); + + $this->assertSame(11, $this->sshPoller->pollCount); + } + + private function createFlow(string $name, string $state): Flow + { + return new Flow([ + 'name' => $name, + 'state' => $state, + ]); + } + + public function testCancelSucceeds(): void + { + $this->brancherApp->expects($this->once()) + ->method('cancel') + ->with('test-brancher'); + + $this->manager->cancel('test-brancher'); + } + + public function testCancelAlreadyCancelledBrancherContinues(): void + { + $response = $this->createMock(ResponseInterface::class); + $response->method('getStatusCode')->willReturn(400); + $response->method('getBody')->willReturn('["Brancher app test-brancher has already been cancelled."]'); + $exception = new HypernodeApiClientException($response); + + $this->brancherApp->expects($this->once()) + ->method('cancel') + ->with('test-brancher') + ->willThrowException($exception); + + $this->logger->expects($this->exactly(2)) + ->method('info'); + + // Should not throw + $this->manager->cancel('test-brancher'); + } + + public function testCancelNotFoundBrancherContinues(): void + { + $response = $this->createMock(ResponseInterface::class); + $response->method('getStatusCode')->willReturn(404); + $response->method('getBody')->willReturn('Not Found'); + $exception = new HypernodeApiClientException($response); + + $this->brancherApp->expects($this->once()) + ->method('cancel') + ->with('test-brancher') + ->willThrowException($exception); + + $this->logger->expects($this->exactly(2)) + ->method('info'); + + // Should not throw + $this->manager->cancel('test-brancher'); + } + + public function testCancelOtherClientErrorPropagates(): void + { + $response = $this->createMock(ResponseInterface::class); + $response->method('getStatusCode')->willReturn(403); + $response->method('getBody')->willReturn('Forbidden'); + $exception = new HypernodeApiClientException($response); + + $this->brancherApp->expects($this->once()) + ->method('cancel') + ->with('test-brancher') + ->willThrowException($exception); + + $this->expectException(HypernodeApiClientException::class); + + $this->manager->cancel('test-brancher'); + } + + public function testCancelMultipleBranchersPartialFailure(): void + { + $response = $this->createMock(ResponseInterface::class); + $response->method('getStatusCode')->willReturn(400); + $response->method('getBody')->willReturn('["Brancher app brancher-1 has already been cancelled."]'); + $exception = new HypernodeApiClientException($response); + + $this->brancherApp->expects($this->exactly(2)) + ->method('cancel') + ->willReturnCallback(function (string $name) use ($exception) { + if ($name === 'brancher-1') { + throw $exception; + } + // brancher-2 succeeds + }); + + // Should not throw, should continue to second brancher + $this->manager->cancel('brancher-1', 'brancher-2'); + } +} diff --git a/tests/Unit/Brancher/TestSshPoller.php b/tests/Unit/Brancher/TestSshPoller.php new file mode 100644 index 0000000..11c084b --- /dev/null +++ b/tests/Unit/Brancher/TestSshPoller.php @@ -0,0 +1,73 @@ +currentMicrotime = $time; + } + + /** + * Advance microtime by a specific amount + */ + public function advanceMicrotime(float $seconds): void + { + $this->currentMicrotime += $seconds; + } + + public function poll(string $hostname): bool + { + $this->pollCount++; + if ($this->pollIndex >= count($this->pollResults)) { + return false; // Default to false if we run out of results + } + return $this->pollResults[$this->pollIndex++]; + } + + public function sleep(int $seconds): void + { + $this->sleepCount++; + $this->sleepCalls[] = $seconds; + + // Advance simulated time + if ($this->sleepTimeAdvance > 0) { + $this->currentMicrotime += $this->sleepTimeAdvance; + } else { + $this->currentMicrotime += $seconds; + } + // Don't actually sleep in tests + } + + public function microtime(): float + { + return $this->currentMicrotime; + } +} diff --git a/tests/Unit/Console/Output/ConsoleLoggerTest.php b/tests/Unit/Console/Output/ConsoleLoggerTest.php new file mode 100644 index 0000000..7bde375 --- /dev/null +++ b/tests/Unit/Console/Output/ConsoleLoggerTest.php @@ -0,0 +1,305 @@ +output = $this->createMock(OutputInterface::class); + $this->output->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_NORMAL); + $this->logger = new ConsoleLogger($this->output); + } + + public function testLogInterpolatesStringPlaceholder(): void + { + $this->output->expects($this->once()) + ->method('writeln') + ->with( + $this->stringContains('Hello World'), + $this->anything() + ); + + $this->logger->info('Hello {name}', ['name' => 'World']); + } + + public function testLogInterpolatesIntegerPlaceholder(): void + { + $this->output->expects($this->once()) + ->method('writeln') + ->with( + $this->stringContains('Count: 42'), + $this->anything() + ); + + $this->logger->info('Count: {count}', ['count' => 42]); + } + + public function testLogInterpolatesFloatPlaceholder(): void + { + $this->output->expects($this->once()) + ->method('writeln') + ->with( + $this->stringContains('Value: 3.14'), + $this->anything() + ); + + $this->logger->info('Value: {value}', ['value' => 3.14]); + } + + public function testLogInterpolatesBooleanPlaceholder(): void + { + $this->output->expects($this->once()) + ->method('writeln') + ->with( + $this->stringContains('Active: 1'), + $this->anything() + ); + + $this->logger->info('Active: {active}', ['active' => true]); + } + + public function testLogInterpolatesObjectWithToString(): void + { + $object = new class { + public function __toString(): string + { + return 'StringableObject'; + } + }; + + $this->output->expects($this->once()) + ->method('writeln') + ->with( + $this->stringContains('Object: StringableObject'), + $this->anything() + ); + + $this->logger->info('Object: {obj}', ['obj' => $object]); + } + + public function testLogInterpolatesDateTimeInterface(): void + { + $date = new DateTime('2024-01-15T10:30:00+00:00'); + + $this->output->expects($this->once()) + ->method('writeln') + ->with( + $this->stringContains('Date: 2024-01-15T10:30:00+00:00'), + $this->anything() + ); + + $this->logger->info('Date: {date}', ['date' => $date]); + } + + public function testLogInterpolatesObjectWithoutToStringAsClassName(): void + { + $object = new \stdClass(); + + $this->output->expects($this->once()) + ->method('writeln') + ->with( + $this->stringContains('[object stdClass]'), + $this->anything() + ); + + $this->logger->info('Object: {obj}', ['obj' => $object]); + } + + public function testLogInterpolatesArrayAsArrayType(): void + { + $this->output->expects($this->once()) + ->method('writeln') + ->with( + $this->stringContains('[array]'), + $this->anything() + ); + + $this->logger->info('Data: {data}', ['data' => ['foo', 'bar']]); + } + + public function testLogInterpolatesNullAsEmpty(): void + { + $this->output->expects($this->once()) + ->method('writeln') + ->with( + $this->stringContains('Value: '), + $this->anything() + ); + + $this->logger->info('Value: {value}', ['value' => null]); + } + + public function testLogWithoutPlaceholdersReturnsMessageUnchanged(): void + { + $this->output->expects($this->once()) + ->method('writeln') + ->with( + $this->stringContains('Simple message'), + $this->anything() + ); + + $this->logger->info('Simple message'); + } + + public function testLogThrowsExceptionForInvalidLevel(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The log level "invalid" does not exist.'); + + $this->logger->log('invalid', 'test message'); + } + + public function testHasErroredReturnsFalseInitially(): void + { + $this->assertSame(false, $this->logger->hasErrored()); + } + + public function testHasErroredReturnsTrueAfterErrorLog(): void + { + $this->logger->error('An error occurred'); + + $this->assertSame(true, $this->logger->hasErrored()); + } + + public function testHasErroredReturnsTrueAfterCriticalLog(): void + { + $this->logger->critical('Critical error'); + + $this->assertSame(true, $this->logger->hasErrored()); + } + + public function testHasErroredReturnsFalseAfterInfoLog(): void + { + $this->logger->info('Info message'); + + $this->assertSame(false, $this->logger->hasErrored()); + } + + public function testErrorLevelWritesToErrorOutput(): void + { + $errorOutput = $this->createMock(OutputInterface::class); + $errorOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_NORMAL); + $errorOutput->expects($this->once()) + ->method('writeln') + ->with( + $this->stringContains('error message'), + $this->anything() + ); + + $consoleOutput = $this->createMock(ConsoleOutputInterface::class); + $consoleOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_NORMAL); + $consoleOutput->method('getErrorOutput')->willReturn($errorOutput); + + $logger = new ConsoleLogger($consoleOutput); + $logger->error('error message'); + } + + public function testDebugLevelNotWrittenAtNormalVerbosity(): void + { + $this->output->expects($this->never())->method('writeln'); + + $this->logger->debug('debug message'); + } + + public function testDebugLevelWrittenAtVerboseLevel(): void + { + $verboseOutput = $this->createMock(OutputInterface::class); + $verboseOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_VERBOSE); + $verboseOutput->expects($this->once()) + ->method('writeln') + ->with( + $this->stringContains('debug message'), + $this->anything() + ); + + $logger = new ConsoleLogger($verboseOutput); + $logger->debug('debug message'); + } + + // Additional log level tests + + public function testHasErroredReturnsTrueAfterEmergencyLog(): void + { + $this->logger->emergency('Emergency error'); + + $this->assertSame(true, $this->logger->hasErrored()); + } + + public function testHasErroredReturnsTrueAfterAlertLog(): void + { + $this->logger->alert('Alert error'); + + $this->assertSame(true, $this->logger->hasErrored()); + } + + public function testHasErroredReturnsFalseAfterWarningLog(): void + { + $this->logger->warning('Warning message'); + + $this->assertSame(false, $this->logger->hasErrored()); + } + + public function testHasErroredReturnsFalseAfterNoticeLog(): void + { + $this->logger->notice('Notice message'); + + $this->assertSame(false, $this->logger->hasErrored()); + } + + public function testCustomVerbosityLevelMapIsUsed(): void + { + // Create custom map where info requires verbose + $customVerbosityMap = [ + LogLevel::INFO => OutputInterface::VERBOSITY_VERBOSE, + ]; + + $normalOutput = $this->createMock(OutputInterface::class); + $normalOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_NORMAL); + $normalOutput->expects($this->never())->method('writeln'); + + $logger = new ConsoleLogger($normalOutput, $customVerbosityMap); + $logger->info('This should not be written at normal verbosity'); + } + + public function testCustomFormatLevelMapIsUsed(): void + { + // Create custom map where warning is treated as error (writes to error output) + $customFormatMap = [ + LogLevel::WARNING => ConsoleLogger::ERROR, + ]; + + $errorOutput = $this->createMock(OutputInterface::class); + $errorOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_NORMAL); + $errorOutput->expects($this->once()) + ->method('writeln') + ->with( + $this->stringContains('warning treated as error'), + $this->anything() + ); + + $consoleOutput = $this->createMock(ConsoleOutputInterface::class); + $consoleOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_NORMAL); + $consoleOutput->method('getErrorOutput')->willReturn($errorOutput); + + $logger = new ConsoleLogger($consoleOutput, [], $customFormatMap); + $logger->warning('warning treated as error'); + + // Warning with custom format map should set errored flag + $this->assertSame(true, $logger->hasErrored()); + } +} diff --git a/tests/Unit/Console/Output/OutputWatcherTest.php b/tests/Unit/Console/Output/OutputWatcherTest.php new file mode 100644 index 0000000..ea9df1b --- /dev/null +++ b/tests/Unit/Console/Output/OutputWatcherTest.php @@ -0,0 +1,185 @@ +wrappedOutput = $this->createMock(OutputInterface::class); + $this->wrappedOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_NORMAL); + $this->watcher = new OutputWatcher($this->wrappedOutput); + } + + public function testGetWasWrittenReturnsFalseInitially(): void + { + $this->assertSame(false, $this->watcher->getWasWritten()); + } + + public function testWriteSetsWasWrittenToTrue(): void + { + $this->watcher->write('test message'); + + $this->assertSame(true, $this->watcher->getWasWritten()); + } + + public function testWritelnSetsWasWrittenToTrue(): void + { + $this->watcher->writeln('test message'); + + $this->assertSame(true, $this->watcher->getWasWritten()); + } + + public function testSetWasWrittenCanResetFlag(): void + { + $this->watcher->write('test'); + $this->assertSame(true, $this->watcher->getWasWritten()); + + $this->watcher->setWasWritten(false); + $this->assertSame(false, $this->watcher->getWasWritten()); + } + + public function testSetWasWrittenCanSetFlagDirectly(): void + { + $this->watcher->setWasWritten(true); + $this->assertSame(true, $this->watcher->getWasWritten()); + } + + public function testWriteDelegatesToWrappedOutput(): void + { + $this->wrappedOutput->expects($this->once()) + ->method('write') + ->with('test message', false, OutputInterface::OUTPUT_NORMAL); + + $this->watcher->write('test message'); + } + + public function testWritelnDelegatesToWrappedOutputWithNewline(): void + { + $this->wrappedOutput->expects($this->once()) + ->method('write') + ->with('test message', true, OutputInterface::OUTPUT_NORMAL); + + $this->watcher->writeln('test message'); + } + + public function testGetVerbosityDelegatesToWrappedOutput(): void + { + $verboseOutput = $this->createMock(OutputInterface::class); + $verboseOutput->expects($this->atLeastOnce()) + ->method('getVerbosity') + ->willReturn(OutputInterface::VERBOSITY_VERBOSE); + + $watcher = new OutputWatcher($verboseOutput); + + $this->assertSame(OutputInterface::VERBOSITY_VERBOSE, $watcher->getVerbosity()); + } + + public function testSetVerbosityDelegatesToWrappedOutput(): void + { + $this->wrappedOutput->expects($this->once()) + ->method('setVerbosity') + ->with(OutputInterface::VERBOSITY_DEBUG); + + $this->watcher->setVerbosity(OutputInterface::VERBOSITY_DEBUG); + } + + public function testIsQuietReturnsTrueWhenVerbosityIsQuiet(): void + { + $quietOutput = $this->createMock(OutputInterface::class); + $quietOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_QUIET); + + $watcher = new OutputWatcher($quietOutput); + + $this->assertSame(true, $watcher->isQuiet()); + } + + public function testIsQuietReturnsFalseWhenVerbosityIsNormal(): void + { + $this->assertSame(false, $this->watcher->isQuiet()); + } + + public function testIsVerboseReturnsTrueWhenVerbosityIsVerbose(): void + { + $verboseOutput = $this->createMock(OutputInterface::class); + $verboseOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_VERBOSE); + + $watcher = new OutputWatcher($verboseOutput); + + $this->assertSame(true, $watcher->isVerbose()); + } + + public function testIsVerboseReturnsFalseWhenVerbosityIsNormal(): void + { + $this->assertSame(false, $this->watcher->isVerbose()); + } + + public function testIsVeryVerboseReturnsTrueWhenVerbosityIsVeryVerbose(): void + { + $veryVerboseOutput = $this->createMock(OutputInterface::class); + $veryVerboseOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_VERY_VERBOSE); + + $watcher = new OutputWatcher($veryVerboseOutput); + + $this->assertSame(true, $watcher->isVeryVerbose()); + } + + public function testIsDebugReturnsTrueWhenVerbosityIsDebug(): void + { + $debugOutput = $this->createMock(OutputInterface::class); + $debugOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_DEBUG); + + $watcher = new OutputWatcher($debugOutput); + + $this->assertSame(true, $watcher->isDebug()); + } + + public function testIsDecoratedDelegatesToWrappedOutput(): void + { + $this->wrappedOutput->expects($this->once()) + ->method('isDecorated') + ->willReturn(true); + + $this->assertSame(true, $this->watcher->isDecorated()); + } + + public function testSetDecoratedDelegatesToWrappedOutput(): void + { + $this->wrappedOutput->expects($this->once()) + ->method('setDecorated') + ->with(true); + + $this->watcher->setDecorated(true); + } + + public function testGetFormatterDelegatesToWrappedOutput(): void + { + $formatter = $this->createMock(OutputFormatterInterface::class); + $this->wrappedOutput->expects($this->once()) + ->method('getFormatter') + ->willReturn($formatter); + + $this->assertSame($formatter, $this->watcher->getFormatter()); + } + + public function testSetFormatterDelegatesToWrappedOutput(): void + { + $formatter = $this->createMock(OutputFormatterInterface::class); + $this->wrappedOutput->expects($this->once()) + ->method('setFormatter') + ->with($formatter); + + $this->watcher->setFormatter($formatter); + } +} diff --git a/tests/Unit/Deployer/FunctionsTest.php b/tests/Unit/Deployer/FunctionsTest.php new file mode 100644 index 0000000..2285508 --- /dev/null +++ b/tests/Unit/Deployer/FunctionsTest.php @@ -0,0 +1,96 @@ +assertSame('first_value', $result); + } + + public function testGetenvFallbackReturnsSecondWhenFirstNotSet(): void + { + putenv(self::ENV_VAR_1); + putenv(self::ENV_VAR_2 . '=second_value'); + + $result = getenvFallback([self::ENV_VAR_1, self::ENV_VAR_2]); + + $this->assertSame('second_value', $result); + } + + public function testGetenvFallbackReturnsThirdWhenFirstTwoNotSet(): void + { + putenv(self::ENV_VAR_1); + putenv(self::ENV_VAR_2); + putenv(self::ENV_VAR_3 . '=third_value'); + + $result = getenvFallback([self::ENV_VAR_1, self::ENV_VAR_2, self::ENV_VAR_3]); + + $this->assertSame('third_value', $result); + } + + public function testGetenvFallbackThrowsExceptionWhenNoneSet(): void + { + putenv(self::ENV_VAR_1); + putenv(self::ENV_VAR_2); + + $this->expectException(EnvironmentVariableNotDefinedException::class); + $this->expectExceptionMessage( + 'None of the requested environment variables ' . self::ENV_VAR_1 . ', ' . self::ENV_VAR_2 . ' is defined' + ); + + getenvFallback([self::ENV_VAR_1, self::ENV_VAR_2]); + } + + public function testGetenvFallbackWithEmptyArrayThrowsException(): void + { + $this->expectException(EnvironmentVariableNotDefinedException::class); + $this->expectExceptionMessage('None of the requested environment variables is defined'); + + getenvFallback([]); + } + + // Tests for noop() function + + public function testNoopReturnsCallable(): void + { + $result = noop(); + + $this->assertInstanceOf(\Closure::class, $result); + } + + public function testNoopReturnedClosureDoesNothing(): void + { + $closure = noop(); + + // Should execute without error and return nothing + $result = $closure(); + + $this->assertNull($result); + } +} diff --git a/tests/Unit/Deployer/Task/After/CachetoolTaskTest.php b/tests/Unit/Deployer/Task/After/CachetoolTaskTest.php new file mode 100644 index 0000000..4c07ae7 --- /dev/null +++ b/tests/Unit/Deployer/Task/After/CachetoolTaskTest.php @@ -0,0 +1,85 @@ +task = new CachetoolTask(); + } + + /** + * @dataProvider phpVersionToCachetoolUrlProvider + */ + public function testGetCachetoolUrlReturnsCorrectVersionForPhpVersion( + string $phpVersion, + string $expectedUrlPart + ): void { + $result = $this->task->getCachetoolUrl($phpVersion); + + $this->assertStringContainsString($expectedUrlPart, $result); + } + + /** + * @return array + */ + public static function phpVersionToCachetoolUrlProvider(): array + { + return [ + // PHP 8.1+ should get cachetool 10.x + 'PHP 8.3.15 gets cachetool 10' => ['8.3.15', '10.0.0'], + 'PHP 8.2.0 gets cachetool 10' => ['8.2.0', '10.0.0'], + 'PHP 8.1.0 gets cachetool 10' => ['8.1.0', '10.0.0'], + 'PHP 8.1.27 gets cachetool 10' => ['8.1.27', '10.0.0'], + + // PHP 8.0.x should get cachetool 8.x + 'PHP 8.0.0 gets cachetool 8' => ['8.0.0', '8.6.1'], + 'PHP 8.0.30 gets cachetool 8' => ['8.0.30', '8.6.1'], + + // PHP 7.3+ should get cachetool 7.x + 'PHP 7.4.33 gets cachetool 7' => ['7.4.33', '7.1.0'], + 'PHP 7.3.0 gets cachetool 7' => ['7.3.0', '7.1.0'], + 'PHP 7.3.33 gets cachetool 7' => ['7.3.33', '7.1.0'], + + // PHP 7.2.x should get cachetool 5.x + 'PHP 7.2.0 gets cachetool 5' => ['7.2.0', '5.1.3'], + 'PHP 7.2.34 gets cachetool 5' => ['7.2.34', '5.1.3'], + + // PHP 7.1.x should get cachetool 4.x + 'PHP 7.1.0 gets cachetool 4' => ['7.1.0', '4.1.1'], + 'PHP 7.1.33 gets cachetool 4' => ['7.1.33', '4.1.1'], + + // Unsupported/old PHP versions fall back to latest (10.x) + 'PHP 7.0.33 falls back to cachetool 10' => ['7.0.33', '10.0.0'], + 'PHP 5.6.40 falls back to cachetool 10' => ['5.6.40', '10.0.0'], + + // Future PHP versions should get latest (10.x) + 'PHP 9.0.0 gets cachetool 10' => ['9.0.0', '10.0.0'], + 'PHP 8.4.0 gets cachetool 10' => ['8.4.0', '10.0.0'], + ]; + } + + public function testGetCachetoolUrlReturnsFullGithubUrl(): void + { + $result = $this->task->getCachetoolUrl('8.2.0'); + + $this->assertStringStartsWith('https://github.com/gordalina/cachetool/releases/download/', $result); + $this->assertStringEndsWith('/cachetool.phar', $result); + } + + public function testGetCachetoolUrlForPhp71ReturnsLegacyUrl(): void + { + $result = $this->task->getCachetoolUrl('7.1.0'); + + // Cachetool 4.x uses a different URL format (gordalina.github.io) + $this->assertStringStartsWith('https://gordalina.github.io/cachetool/downloads/', $result); + } +} diff --git a/tests/Unit/Deployer/Task/Build/ComposerAuthTaskGlobalTest.php b/tests/Unit/Deployer/Task/Build/ComposerAuthTaskGlobalTest.php new file mode 100644 index 0000000..9d715b1 --- /dev/null +++ b/tests/Unit/Deployer/Task/Build/ComposerAuthTaskGlobalTest.php @@ -0,0 +1,62 @@ +getAuthContent(); + + $this->assertSame($jsonContent, $result); + } + + public function testGetAuthContentReturnsRawJsonWhenNotBase64Encoded(): void + { + $jsonContent = '{"http-basic":{"repo.example.com":{"username":"user","password":"pass"}}}'; + putenv(ComposerAuthTaskGlobal::ENV_COMPOSER_AUTH . '=' . $jsonContent); + + $task = new ComposerAuthTaskGlobal(); + $result = $task->getAuthContent(); + + $this->assertSame($jsonContent, $result); + } + + public function testGetAuthContentThrowsJsonExceptionForInvalidContent(): void + { + $invalidContent = 'this-is-not-valid-json-or-base64'; + putenv(ComposerAuthTaskGlobal::ENV_COMPOSER_AUTH . '=' . $invalidContent); + + $task = new ComposerAuthTaskGlobal(); + + $this->expectException(JsonException::class); + $task->getAuthContent(); + } + + public function testGetAuthContentThrowsExceptionWhenEnvVarNotSet(): void + { + putenv(ComposerAuthTaskGlobal::ENV_COMPOSER_AUTH); + + $task = new ComposerAuthTaskGlobal(); + + $this->expectException(EnvironmentVariableNotDefinedException::class); + $task->getAuthContent(); + } +} diff --git a/tests/Unit/Deployer/Task/Build/HighPerformanceStaticDeployTaskTest.php b/tests/Unit/Deployer/Task/Build/HighPerformanceStaticDeployTaskTest.php new file mode 100644 index 0000000..5769c76 --- /dev/null +++ b/tests/Unit/Deployer/Task/Build/HighPerformanceStaticDeployTaskTest.php @@ -0,0 +1,92 @@ +task = new HighPerformanceStaticDeployTask(); + } + + public function testIsEnabledReturnsFalseWhenNotConfigured(): void + { + $config = $this->createMock(Configuration::class); + $config->method('getVariables')->willReturn([]); + + $this->assertFalse($this->task->isEnabled($config)); + } + + public function testIsEnabledReturnsTrueWhenEnabledInVariables(): void + { + $config = $this->createMock(Configuration::class); + $config->method('getVariables') + ->willReturnCallback(fn(string $stage = 'all') => match ($stage) { + 'all' => ['high_performance_static_deploy' => true], + default => [], + }); + + $this->assertTrue($this->task->isEnabled($config)); + } + + public function testIsEnabledReturnsTrueWhenEnabledInBuildVariables(): void + { + $config = $this->createMock(Configuration::class); + $config->method('getVariables') + ->willReturnCallback(fn(string $stage = 'all') => match ($stage) { + 'build' => ['high_performance_static_deploy' => true], + default => [], + }); + + $this->assertTrue($this->task->isEnabled($config)); + } + + public function testIsEnabledReturnsFalseWhenExplicitlyDisabled(): void + { + $config = $this->createMock(Configuration::class); + $config->method('getVariables') + ->willReturnCallback(fn(string $stage = 'all') => match ($stage) { + 'all' => ['high_performance_static_deploy' => false], + default => [], + }); + + $this->assertFalse($this->task->isEnabled($config)); + } + + public function testBuildThemeArgsWithSingleTheme(): void + { + $themes = ['Vendor/theme' => 'nl_NL en_US']; + + $result = $this->task->buildThemeArgs($themes); + + $this->assertSame('--theme=Vendor/theme', $result); + } + + public function testBuildThemeArgsWithMultipleThemes(): void + { + $themes = [ + 'Vendor/theme1' => 'nl_NL', + 'Vendor/theme2' => 'en_US', + 'Vendor/theme3' => 'de_DE', + ]; + + $result = $this->task->buildThemeArgs($themes); + + $this->assertSame('--theme=Vendor/theme1 --theme=Vendor/theme2 --theme=Vendor/theme3', $result); + } + + public function testBuildThemeArgsWithEmptyArray(): void + { + $result = $this->task->buildThemeArgs([]); + + $this->assertSame('', $result); + } +} diff --git a/tests/Unit/Deployer/Task/IncrementedTaskTraitTest.php b/tests/Unit/Deployer/Task/IncrementedTaskTraitTest.php new file mode 100644 index 0000000..37f1bad --- /dev/null +++ b/tests/Unit/Deployer/Task/IncrementedTaskTraitTest.php @@ -0,0 +1,75 @@ +assertSame('test:task:0', $task->getTaskName()); + $this->assertSame('test:task:1', $task->getTaskName()); + $this->assertSame('test:task:2', $task->getTaskName()); + } + + public function testGetTaskNameWithIdentifierIncludesIdentifier(): void + { + $task = new TestIncrementedTask(); + + $result = $task->getTaskName('foo'); + + $this->assertSame('test:task:foo:0', $result); + } + + public function testGetTaskNameWithIdentifierIncrementsCounter(): void + { + $task = new TestIncrementedTask(); + + $this->assertSame('test:task:foo:0', $task->getTaskName('foo')); + $this->assertSame('test:task:bar:1', $task->getTaskName('bar')); + $this->assertSame('test:task:foo:2', $task->getTaskName('foo')); + } + + public function testGetTaskNameMixedWithAndWithoutIdentifier(): void + { + $task = new TestIncrementedTask(); + + $this->assertSame('test:task:0', $task->getTaskName()); + $this->assertSame('test:task:foo:1', $task->getTaskName('foo')); + $this->assertSame('test:task:2', $task->getTaskName()); + } + + public function testGetRegisteredTasksReturnsAllGeneratedNames(): void + { + $task = new TestIncrementedTask(); + + $task->getTaskName(); + $task->getTaskName('foo'); + $task->getTaskName(); + + $this->assertSame( + ['test:task:0', 'test:task:foo:1', 'test:task:2'], + $task->getRegisteredTasks() + ); + } + + public function testGetRegisteredTasksReturnsEmptyArrayInitially(): void + { + $task = new TestIncrementedTask(); + + $this->assertSame([], $task->getRegisteredTasks()); + } + + public function testGetTaskNameWithEmptyIdentifierTreatedAsNoIdentifier(): void + { + $task = new TestIncrementedTask(); + + // Empty string should be treated same as no identifier due to !empty() check + $this->assertSame('test:task:0', $task->getTaskName('')); + } +} diff --git a/tests/Unit/Deployer/Task/TestIncrementedTask.php b/tests/Unit/Deployer/Task/TestIncrementedTask.php new file mode 100644 index 0000000..ad6c3e1 --- /dev/null +++ b/tests/Unit/Deployer/Task/TestIncrementedTask.php @@ -0,0 +1,23 @@ +output = $this->createMock(OutputInterface::class); + $this->printer = new GithubWorkflowPrinter($this->output); + } + + public function testContentHasWorkflowCommandReturnsTrueForWarningCommand(): void + { + $this->assertSame(true, $this->printer->contentHasWorkflowCommand('::warning::some message')); + } + + public function testContentHasWorkflowCommandReturnsTrueForErrorCommand(): void + { + $this->assertSame(true, $this->printer->contentHasWorkflowCommand('::error::something went wrong')); + } + + public function testContentHasWorkflowCommandReturnsTrueForSetOutputCommand(): void + { + $this->assertSame(true, $this->printer->contentHasWorkflowCommand('::set-output name=foo::bar')); + } + + public function testContentHasWorkflowCommandReturnsTrueForDebugCommand(): void + { + $this->assertSame(true, $this->printer->contentHasWorkflowCommand('::debug::debug info')); + } + + public function testContentHasWorkflowCommandReturnsTrueForGroupCommand(): void + { + $this->assertSame(true, $this->printer->contentHasWorkflowCommand('::group::My Group')); + } + + public function testContentHasWorkflowCommandReturnsTrueForEndGroupCommand(): void + { + $this->assertSame(true, $this->printer->contentHasWorkflowCommand('::endgroup::')); + } + + public function testContentHasWorkflowCommandReturnsFalseForRegularText(): void + { + $this->assertSame(false, $this->printer->contentHasWorkflowCommand('Hello world')); + } + + public function testContentHasWorkflowCommandReturnsFalseForEmptyString(): void + { + $this->assertSame(false, $this->printer->contentHasWorkflowCommand('')); + } + + public function testContentHasWorkflowCommandReturnsFalseForPartialCommand(): void + { + $this->assertSame(false, $this->printer->contentHasWorkflowCommand('::invalid')); + } + + public function testContentHasWorkflowCommandReturnsFalseForColonsWithoutCommand(): void + { + $this->assertSame(false, $this->printer->contentHasWorkflowCommand(':: ::')); + } + + public function testContentHasWorkflowCommandReturnsTrueForCommandInMultilineContent(): void + { + $content = "Some regular output\n::warning::a warning message\nMore output"; + $this->assertSame(true, $this->printer->contentHasWorkflowCommand($content)); + } + + public function testContentHasWorkflowCommandReturnsTrueForCommandWithFileAndLine(): void + { + $this->assertSame(true, $this->printer->contentHasWorkflowCommand('::error file=app.js,line=10::error message')); + } + + // Tests for writeln() method + + public function testWritelnOutputsWorkflowCommandDirectlyForStdout(): void + { + $host = $this->createMock(Host::class); + + $this->output->expects($this->once()) + ->method('writeln') + ->with('::warning::test message'); + + $this->printer->writeln(Process::OUT, $host, '::warning::test message'); + } + + public function testWritelnDoesNotOutputEmptyLine(): void + { + $host = $this->createMock(Host::class); + + $this->output->expects($this->never()) + ->method('writeln'); + + $this->printer->writeln(Process::OUT, $host, ''); + } + + public function testWritelnDelegatesToParentForNonWorkflowStdout(): void + { + $host = $this->createMock(Host::class); + $host->method('__toString')->willReturn('test-host'); + + // Parent's writeln will be called which writes "[host] line" format + $this->output->expects($this->once()) + ->method('writeln') + ->with($this->stringContains('regular output')); + + $this->printer->writeln(Process::OUT, $host, 'regular output'); + } + + public function testWritelnDelegatesToParentForStderr(): void + { + $host = $this->createMock(Host::class); + $host->method('__toString')->willReturn('test-host'); + + // Even workflow commands on stderr should go through parent + $this->output->expects($this->once()) + ->method('writeln') + ->with($this->stringContains('::warning::')); + + $this->printer->writeln(Process::ERR, $host, '::warning::error stream message'); + } + + // Tests for callback() method + + public function testCallbackPrintsWhenForceOutputIsTrue(): void + { + $host = $this->createMock(Host::class); + $host->method('__toString')->willReturn('test-host'); + + $this->output->method('isVerbose')->willReturn(false); + $this->output->expects($this->once()) + ->method('writeln') + ->with($this->stringContains('forced output')); + + $callback = $this->printer->callback($host, true); + $callback(Process::OUT, 'forced output'); + } + + public function testCallbackPrintsWhenVerbose(): void + { + $host = $this->createMock(Host::class); + $host->method('__toString')->willReturn('test-host'); + + $this->output->method('isVerbose')->willReturn(true); + $this->output->expects($this->once()) + ->method('writeln') + ->with($this->stringContains('verbose output')); + + $callback = $this->printer->callback($host, false); + $callback(Process::OUT, 'verbose output'); + } + + public function testCallbackPrintsWorkflowCommandOnStdoutEvenWhenNotVerbose(): void + { + $host = $this->createMock(Host::class); + + $this->output->method('isVerbose')->willReturn(false); + $this->output->expects($this->once()) + ->method('writeln') + ->with('::warning::workflow command'); + + $callback = $this->printer->callback($host, false); + $callback(Process::OUT, '::warning::workflow command'); + } + + public function testCallbackDoesNotPrintRegularOutputWhenNotVerboseAndNotForced(): void + { + $host = $this->createMock(Host::class); + + $this->output->method('isVerbose')->willReturn(false); + $this->output->expects($this->never())->method('writeln'); + + $callback = $this->printer->callback($host, false); + $callback(Process::OUT, 'regular output that should be suppressed'); + } + + public function testCallbackDoesNotPrintWorkflowCommandOnStderrWhenNotVerbose(): void + { + $host = $this->createMock(Host::class); + + $this->output->method('isVerbose')->willReturn(false); + // Workflow command on stderr should NOT be printed when not verbose/forced + // because the condition checks $type == Process::OUT + $this->output->expects($this->never())->method('writeln'); + + $callback = $this->printer->callback($host, false); + $callback(Process::ERR, '::warning::workflow on stderr'); + } +} diff --git a/tests/Unit/Report/ReportLoaderTest.php b/tests/Unit/Report/ReportLoaderTest.php new file mode 100644 index 0000000..dbb7455 --- /dev/null +++ b/tests/Unit/Report/ReportLoaderTest.php @@ -0,0 +1,126 @@ +tempDir = sys_get_temp_dir() . '/hypernode-deploy-test-' . uniqid(); + mkdir($this->tempDir, 0777, true); + $this->reportPath = $this->tempDir . '/' . Report::REPORT_FILENAME; + } + + protected function tearDown(): void + { + if (file_exists($this->reportPath)) { + unlink($this->reportPath); + } + if (is_dir($this->tempDir)) { + rmdir($this->tempDir); + } + } + + public function testLoadReportReturnsNullWhenFileDoesNotExist(): void + { + $loader = new ReportLoader($this->reportPath); + + $result = $loader->loadReport(); + + $this->assertNull($result); + } + + public function testLoadReportReturnsReportWhenFileExists(): void + { + $data = [ + 'version' => 'v1', + 'stage' => 'production', + 'hostnames' => ['app.hypernode.io'], + 'brancher_hypernodes' => ['app-ephabc123.hypernode.io'], + ]; + file_put_contents($this->reportPath, json_encode($data)); + + $loader = new ReportLoader($this->reportPath); + + $result = $loader->loadReport(); + + $this->assertInstanceOf(Report::class, $result); + } + + public function testLoadReportParsesJsonCorrectly(): void + { + $data = [ + 'version' => 'v1', + 'stage' => 'staging', + 'hostnames' => ['staging.hypernode.io', 'staging2.hypernode.io'], + 'brancher_hypernodes' => ['staging-ephabc123.hypernode.io'], + ]; + file_put_contents($this->reportPath, json_encode($data)); + + $loader = new ReportLoader($this->reportPath); + + $result = $loader->loadReport(); + + $this->assertSame('v1', $result->getVersion()); + $this->assertSame('staging', $result->getStage()); + $this->assertSame(['staging.hypernode.io', 'staging2.hypernode.io'], $result->getHostnames()); + $this->assertSame(['staging-ephabc123.hypernode.io'], $result->getBrancherHypernodes()); + } + + public function testLoadReportThrowsExceptionForInvalidJson(): void + { + file_put_contents($this->reportPath, 'this is not valid json'); + + $loader = new ReportLoader($this->reportPath); + + $this->expectException(JsonException::class); + + $loader->loadReport(); + } + + public function testLoadReportHandlesEmptyArrays(): void + { + $data = [ + 'version' => 'v1', + 'stage' => 'test', + 'hostnames' => [], + 'brancher_hypernodes' => [], + ]; + file_put_contents($this->reportPath, json_encode($data)); + + $loader = new ReportLoader($this->reportPath); + + $result = $loader->loadReport(); + + $this->assertSame([], $result->getHostnames()); + $this->assertSame([], $result->getBrancherHypernodes()); + } + + public function testLoadReportRoundTripWithWriter(): void + { + $originalReport = new Report( + 'production', + ['app.hypernode.io', 'app2.hypernode.io'], + ['app-ephabc123.hypernode.io'], + 'v1' + ); + + file_put_contents($this->reportPath, json_encode($originalReport->toArray())); + + $loader = new ReportLoader($this->reportPath); + + $loadedReport = $loader->loadReport(); + + $this->assertSame($originalReport->toArray(), $loadedReport->toArray()); + } +} diff --git a/tests/Unit/Report/ReportTest.php b/tests/Unit/Report/ReportTest.php new file mode 100644 index 0000000..b7a44d6 --- /dev/null +++ b/tests/Unit/Report/ReportTest.php @@ -0,0 +1,88 @@ +toArray(); + + $this->assertSame('v1', $result['version']); + $this->assertSame('production', $result['stage']); + $this->assertSame(['app1.hypernode.io', 'app2.hypernode.io'], $result['hostnames']); + $this->assertSame(['app1-ephabc123.hypernode.io'], $result['brancher_hypernodes']); + } + + public function testFromArrayCreatesReportWithCorrectValues(): void + { + $data = [ + 'version' => 'v1', + 'stage' => 'staging', + 'hostnames' => ['staging.hypernode.io'], + 'brancher_hypernodes' => ['staging-ephabc123.hypernode.io'], + ]; + + $report = Report::fromArray($data); + + $this->assertSame('v1', $report->getVersion()); + $this->assertSame('staging', $report->getStage()); + $this->assertSame(['staging.hypernode.io'], $report->getHostnames()); + $this->assertSame(['staging-ephabc123.hypernode.io'], $report->getBrancherHypernodes()); + } + + public function testToArrayFromArrayRoundTripProducesEqualData(): void + { + $originalData = [ + 'version' => 'v1', + 'stage' => 'production', + 'hostnames' => ['app.hypernode.io'], + 'brancher_hypernodes' => ['app-ephabc123.hypernode.io'], + ]; + + $report = Report::fromArray($originalData); + $resultData = $report->toArray(); + + $this->assertSame($originalData, $resultData); + } + + public function testDefaultVersionIsV1(): void + { + $report = new Report( + 'production', + ['app.hypernode.io'], + ['app-ephabc123.hypernode.io'] + ); + + $this->assertSame(Report::REPORT_VERSION, $report->getVersion()); + $this->assertSame('v1', $report->getVersion()); + } + + public function testEmptyHostnamesArrayIsHandled(): void + { + $report = new Report('production', [], ['app-ephabc123.hypernode.io']); + + $this->assertSame([], $report->getHostnames()); + $this->assertSame([], $report->toArray()['hostnames']); + } + + public function testEmptyBrancherHypernodesArrayIsHandled(): void + { + $report = new Report('production', ['app.hypernode.io'], []); + + $this->assertSame([], $report->getBrancherHypernodes()); + $this->assertSame([], $report->toArray()['brancher_hypernodes']); + } +} diff --git a/tests/Unit/Report/ReportWriterTest.php b/tests/Unit/Report/ReportWriterTest.php new file mode 100644 index 0000000..b5e580e --- /dev/null +++ b/tests/Unit/Report/ReportWriterTest.php @@ -0,0 +1,107 @@ +tempDir = sys_get_temp_dir() . '/hypernode-deploy-test-' . uniqid(); + mkdir($this->tempDir, 0777, true); + $this->reportPath = $this->tempDir . '/' . Report::REPORT_FILENAME; + } + + protected function tearDown(): void + { + if (file_exists($this->reportPath)) { + unlink($this->reportPath); + } + if (is_dir($this->tempDir)) { + rmdir($this->tempDir); + } + } + + public function testWriteCreatesJsonFile(): void + { + $report = new Report('production', ['app.hypernode.io'], ['app-ephabc123.hypernode.io']); + + $writer = new ReportWriter($this->reportPath); + $writer->write($report); + + $this->assertFileExists($this->reportPath); + } + + public function testWriteCreatesValidJson(): void + { + $report = new Report('production', ['app.hypernode.io'], ['app-ephabc123.hypernode.io']); + + $writer = new ReportWriter($this->reportPath); + $writer->write($report); + + $contents = file_get_contents($this->reportPath); + $decoded = json_decode($contents, true); + + $this->assertNotNull($decoded); + $this->assertIsArray($decoded); + } + + public function testWriteContainsCorrectData(): void + { + $report = new Report( + 'staging', + ['staging.hypernode.io', 'staging2.hypernode.io'], + ['staging-ephabc123.hypernode.io'], + 'v1' + ); + + $writer = new ReportWriter($this->reportPath); + $writer->write($report); + + $contents = file_get_contents($this->reportPath); + $decoded = json_decode($contents, true); + + $this->assertSame('v1', $decoded['version']); + $this->assertSame('staging', $decoded['stage']); + $this->assertSame(['staging.hypernode.io', 'staging2.hypernode.io'], $decoded['hostnames']); + $this->assertSame(['staging-ephabc123.hypernode.io'], $decoded['brancher_hypernodes']); + } + + public function testWriteOverwritesExistingFile(): void + { + file_put_contents($this->reportPath, '{"old": "data"}'); + + $report = new Report('production', ['new.hypernode.io'], []); + + $writer = new ReportWriter($this->reportPath); + $writer->write($report); + + $contents = file_get_contents($this->reportPath); + $decoded = json_decode($contents, true); + + $this->assertSame('production', $decoded['stage']); + $this->assertArrayNotHasKey('old', $decoded); + } + + public function testWriteWithEmptyArrays(): void + { + $report = new Report('test', [], []); + + $writer = new ReportWriter($this->reportPath); + $writer->write($report); + + $contents = file_get_contents($this->reportPath); + $decoded = json_decode($contents, true); + + $this->assertSame([], $decoded['hostnames']); + $this->assertSame([], $decoded['brancher_hypernodes']); + } +} diff --git a/tests/Unit/Stdlib/RevisionFinderTest.php b/tests/Unit/Stdlib/RevisionFinderTest.php new file mode 100644 index 0000000..62bf52f --- /dev/null +++ b/tests/Unit/Stdlib/RevisionFinderTest.php @@ -0,0 +1,48 @@ +getRevision(); + + $this->assertSame('abc123def456', $result); + } + + public function testGetRevisionReturnsMasterWhenEnvVarIsNotSet(): void + { + putenv(self::ENV_VAR); + + $finder = new RevisionFinder(); + $result = $finder->getRevision(); + + $this->assertSame('master', $result); + } + + public function testGetRevisionReturnsMasterWhenEnvVarIsEmptyString(): void + { + putenv(self::ENV_VAR . '='); + + $finder = new RevisionFinder(); + $result = $finder->getRevision(); + + $this->assertSame('master', $result); + } +}