Full Code of deployphp/deployer for AI

master 6e15e217ae5f cached
338 files
1014.5 KB
271.2k tokens
739 symbols
1 requests
Download .txt
Showing preview only (1,094K chars total). Download the full file or copy to clipboard to get everything.
Repository: deployphp/deployer
Branch: master
Commit: 6e15e217ae5f
Files: 338
Total size: 1014.5 KB

Directory structure:
gitextract_pd_hfplz/

├── .gitattributes
├── .github/
│   ├── DISCUSSION_TEMPLATE/
│   │   └── bugs.yml
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   └── config.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── labeler.yml
│   └── workflows/
│       ├── check.yml
│       ├── docker.yml
│       ├── docs-sync.yml
│       ├── docs.yml
│       ├── labeler.yml
│       ├── lint.yml
│       ├── release.yml
│       ├── stale.yml
│       └── test.yml
├── .gitignore
├── .php-cs-fixer.dist.php
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── bin/
│   ├── build
│   ├── dep
│   └── docgen
├── composer.json
├── contrib/
│   ├── bugsnag.php
│   ├── cachetool.php
│   ├── chatwork.php
│   ├── cimonitor.php
│   ├── cloudflare.php
│   ├── cpanel.php
│   ├── crontab.php
│   ├── directadmin.php
│   ├── discord.php
│   ├── grafana.php
│   ├── hangouts.php
│   ├── hipchat.php
│   ├── ispmanager.php
│   ├── mattermost.php
│   ├── ms-teams.php
│   ├── newrelic.php
│   ├── npm.php
│   ├── ntfy.php
│   ├── phinx.php
│   ├── php-fpm.php
│   ├── rabbit.php
│   ├── raygun.php
│   ├── rocketchat.php
│   ├── rollbar.php
│   ├── rsync.php
│   ├── sentry.php
│   ├── slack.php
│   ├── supervisord-monitor.php
│   ├── telegram.php
│   ├── webpack_encore.php
│   ├── workplace.php
│   ├── yammer.php
│   └── yarn.php
├── docs/
│   ├── KNOWN_BUGS.md
│   ├── UPGRADE.md
│   ├── api.md
│   ├── avoid-php-fpm-reloading.md
│   ├── basics.md
│   ├── ci-cd.md
│   ├── cli.md
│   ├── contrib/
│   │   ├── README.md
│   │   ├── bugsnag.md
│   │   ├── cachetool.md
│   │   ├── chatwork.md
│   │   ├── cimonitor.md
│   │   ├── cloudflare.md
│   │   ├── cpanel.md
│   │   ├── crontab.md
│   │   ├── directadmin.md
│   │   ├── discord.md
│   │   ├── grafana.md
│   │   ├── hangouts.md
│   │   ├── hipchat.md
│   │   ├── ispmanager.md
│   │   ├── mattermost.md
│   │   ├── ms-teams.md
│   │   ├── newrelic.md
│   │   ├── npm.md
│   │   ├── ntfy.md
│   │   ├── phinx.md
│   │   ├── php-fpm.md
│   │   ├── rabbit.md
│   │   ├── raygun.md
│   │   ├── rocketchat.md
│   │   ├── rollbar.md
│   │   ├── rsync.md
│   │   ├── sentry.md
│   │   ├── slack.md
│   │   ├── supervisord-monitor.md
│   │   ├── telegram.md
│   │   ├── webpack_encore.md
│   │   ├── workplace.md
│   │   ├── yammer.md
│   │   └── yarn.md
│   ├── getting-started.md
│   ├── hosts.md
│   ├── installation.md
│   ├── recipe/
│   │   ├── README.md
│   │   ├── cakephp.md
│   │   ├── codeigniter.md
│   │   ├── codeigniter4.md
│   │   ├── common.md
│   │   ├── composer.md
│   │   ├── contao.md
│   │   ├── craftcms.md
│   │   ├── deploy/
│   │   │   ├── check_remote.md
│   │   │   ├── cleanup.md
│   │   │   ├── clear_paths.md
│   │   │   ├── copy_dirs.md
│   │   │   ├── env.md
│   │   │   ├── info.md
│   │   │   ├── lock.md
│   │   │   ├── push.md
│   │   │   ├── release.md
│   │   │   ├── rollback.md
│   │   │   ├── setup.md
│   │   │   ├── shared.md
│   │   │   ├── symlink.md
│   │   │   ├── update_code.md
│   │   │   ├── vendors.md
│   │   │   └── writable.md
│   │   ├── drupal7.md
│   │   ├── drupal8.md
│   │   ├── flow_framework.md
│   │   ├── fuelphp.md
│   │   ├── joomla.md
│   │   ├── laravel.md
│   │   ├── magento.md
│   │   ├── magento2.md
│   │   ├── pimcore.md
│   │   ├── prestashop.md
│   │   ├── provision/
│   │   │   ├── databases.md
│   │   │   ├── nodejs.md
│   │   │   ├── php.md
│   │   │   ├── user.md
│   │   │   └── website.md
│   │   ├── provision.md
│   │   ├── shopware.md
│   │   ├── silverstripe.md
│   │   ├── spiral.md
│   │   ├── statamic.md
│   │   ├── sulu.md
│   │   ├── symfony.md
│   │   ├── typo3.md
│   │   ├── wordpress.md
│   │   ├── yii.md
│   │   └── zend_framework.md
│   ├── selector.md
│   ├── sidebar.js
│   ├── tasks.md
│   └── yaml.md
├── phpstan.neon
├── phpunit.xml
├── recipe/
│   ├── cakephp.php
│   ├── codeigniter.php
│   ├── codeigniter4.php
│   ├── common.php
│   ├── composer.php
│   ├── contao.php
│   ├── craftcms.php
│   ├── deploy/
│   │   ├── check_remote.php
│   │   ├── cleanup.php
│   │   ├── clear_paths.php
│   │   ├── copy_dirs.php
│   │   ├── env.php
│   │   ├── info.php
│   │   ├── lock.php
│   │   ├── push.php
│   │   ├── release.php
│   │   ├── rollback.php
│   │   ├── setup.php
│   │   ├── shared.php
│   │   ├── symlink.php
│   │   ├── update_code.php
│   │   ├── vendors.php
│   │   └── writable.php
│   ├── drupal7.php
│   ├── drupal8.php
│   ├── flow_framework.php
│   ├── fuelphp.php
│   ├── joomla.php
│   ├── laravel.php
│   ├── magento.php
│   ├── magento2.php
│   ├── pimcore.php
│   ├── prestashop.php
│   ├── provision/
│   │   ├── 404.html
│   │   ├── Caddyfile
│   │   ├── databases.php
│   │   ├── nodejs.php
│   │   ├── php.php
│   │   ├── user.php
│   │   └── website.php
│   ├── provision.php
│   ├── shopware.php
│   ├── silverstripe.php
│   ├── spiral.php
│   ├── statamic.php
│   ├── sulu.php
│   ├── symfony.php
│   ├── typo3.php
│   ├── wordpress.php
│   ├── yii.php
│   └── zend_framework.php
├── src/
│   ├── Collection/
│   │   └── Collection.php
│   ├── Command/
│   │   ├── BlackjackCommand.php
│   │   ├── CommandCommon.php
│   │   ├── ConfigCommand.php
│   │   ├── CustomOption.php
│   │   ├── InitCommand.php
│   │   ├── MainCommand.php
│   │   ├── RunCommand.php
│   │   ├── SelectCommand.php
│   │   ├── SshCommand.php
│   │   ├── TreeCommand.php
│   │   └── WorkerCommand.php
│   ├── Component/
│   │   ├── PharUpdate/
│   │   │   ├── Console/
│   │   │   │   ├── Command.php
│   │   │   │   └── Helper.php
│   │   │   ├── Exception/
│   │   │   │   ├── Exception.php
│   │   │   │   ├── ExceptionInterface.php
│   │   │   │   ├── FileException.php
│   │   │   │   ├── InvalidArgumentException.php
│   │   │   │   └── LogicException.php
│   │   │   ├── Manager.php
│   │   │   ├── Manifest.php
│   │   │   ├── Update.php
│   │   │   └── Version/
│   │   │       ├── Builder.php
│   │   │       ├── Comparator.php
│   │   │       ├── Dumper.php
│   │   │       ├── Exception/
│   │   │       │   ├── InvalidIdentifierException.php
│   │   │       │   ├── InvalidNumberException.php
│   │   │       │   ├── InvalidStringRepresentationException.php
│   │   │       │   └── VersionException.php
│   │   │       ├── Parser.php
│   │   │       ├── Validator.php
│   │   │       └── Version.php
│   │   └── Pimple/
│   │       ├── Container.php
│   │       └── Exception/
│   │           ├── ExpectedInvokableException.php
│   │           ├── FrozenServiceException.php
│   │           ├── InvalidServiceIdentifierException.php
│   │           └── UnknownIdentifierException.php
│   ├── Configuration.php
│   ├── Deployer.php
│   ├── Documentation/
│   │   ├── ApiGen.php
│   │   ├── DocConfig.php
│   │   ├── DocGen.php
│   │   ├── DocRecipe.php
│   │   └── DocTask.php
│   ├── Exception/
│   │   ├── ConfigurationException.php
│   │   ├── Exception.php
│   │   ├── GracefulShutdownException.php
│   │   ├── HttpieException.php
│   │   ├── RunException.php
│   │   ├── TimeoutException.php
│   │   └── WillAskUser.php
│   ├── Executor/
│   │   ├── Master.php
│   │   ├── Messenger.php
│   │   ├── Planner.php
│   │   ├── Response.php
│   │   ├── Server.php
│   │   └── Worker.php
│   ├── Host/
│   │   ├── Host.php
│   │   ├── HostCollection.php
│   │   ├── Localhost.php
│   │   └── Range.php
│   ├── Importer/
│   │   └── Importer.php
│   ├── Logger/
│   │   ├── Handler/
│   │   │   ├── FileHandler.php
│   │   │   ├── HandlerInterface.php
│   │   │   └── NullHandler.php
│   │   └── Logger.php
│   ├── ProcessRunner/
│   │   ├── Printer.php
│   │   └── ProcessRunner.php
│   ├── Selector/
│   │   └── Selector.php
│   ├── Ssh/
│   │   ├── IOArguments.php
│   │   ├── RunParams.php
│   │   └── SshClient.php
│   ├── Support/
│   │   ├── ObjectProxy.php
│   │   ├── Reporter.php
│   │   └── helpers.php
│   ├── Task/
│   │   ├── Context.php
│   │   ├── GroupTask.php
│   │   ├── ScriptManager.php
│   │   ├── Task.php
│   │   └── TaskCollection.php
│   ├── Utility/
│   │   ├── Httpie.php
│   │   └── Rsync.php
│   ├── functions.php
│   └── schema.json
└── tests/
    ├── bootstrap.php
    ├── fixtures/
    │   ├── project/
    │   │   └── uploaded.html
    │   └── repository/
    │       ├── README.md
    │       ├── composer.json
    │       └── uploads/
    │           └── poem.txt
    ├── joy/
    │   ├── HostDefaultConfigTest.php
    │   ├── JoyTest.php
    │   └── OnFuncTest.php
    ├── legacy/
    │   ├── AbstractTest.php
    │   ├── CurrentPathTest.php
    │   ├── DeployTest.php
    │   ├── EnvTest.php
    │   ├── NamedArgumentsTest.php
    │   ├── OncePerNodeTest.php
    │   ├── OnceTest.php
    │   ├── ParallelTest.php
    │   ├── SelectTest.php
    │   ├── UpdateCodeTest.php
    │   ├── YamlTest.php
    │   └── recipe/
    │       ├── deploy.php
    │       ├── deploy.yaml
    │       ├── env.php
    │       ├── once.php
    │       ├── once_per_node.php
    │       ├── parallel.php
    │       ├── select.php
    │       └── update_code.php
    ├── phpstan-baseline.neon
    └── src/
        ├── Collection/
        │   └── CollectionTest.php
        ├── Command/
        │   └── BlackjackCommandTest.php
        ├── Component/
        │   └── Pimple/
        │       └── PimpleTest.php
        ├── Configuration/
        │   └── ConfigurationTest.php
        ├── DeployerTest.php
        ├── FunctionsTest.php
        ├── Host/
        │   ├── ConfigurationTest.php
        │   ├── HostTest.php
        │   └── RangeTest.php
        ├── Importer/
        │   └── ImporterTest.php
        ├── Selector/
        │   └── SelectorTest.php
        ├── Ssh/
        │   └── IOArgumentsTest.php
        ├── Support/
        │   ├── HelpersTest.php
        │   └── ObjectProxyTest.php
        └── Task/
            ├── ContextTest.php
            ├── ScriptManagerTest.php
            └── TaskTest.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
/.gitattributes export-ignore
/.github/ export-ignore
/.gitignore export-ignore
/docs/ export-ignore
/phpcs.xml export-ignore
/phpstan.neon export-ignore
/phpunit.xml export-ignore
/tests/ export-ignore


================================================
FILE: .github/DISCUSSION_TEMPLATE/bugs.yml
================================================
body:
  - type: markdown
    attributes:
      value: |
        **Before opening a bug report, please search the existing discussions.**

  - type: input
    id: deployer-version
    attributes:
      label: Deployer Version
      description: Which version of Deployer are you using? Please provide the full version.
      placeholder: v8.0.0
    validations:
      required: true

  - type: input
    id: target-os
    attributes:
      label: Target OS
      description: Which operating system are you using? Please provide the full version.
      placeholder: Ubuntu 24.04
    validations:
      required: true

  - type: dropdown
    id: php-version
    attributes:
      label: Which PHP version are you using?
      options:
        - PHP 8.5
        - PHP 8.4
        - PHP 8.3
        - PHP 8.2
        - PHP 8.1
        - PHP 8.0
        - PHP 7.4
        - PHP 7.3
        - PHP 7.2
        - PHP 7.1
        - PHP 7.0
        - PHP 5.6
        - PHP 5.5
        - PHP 5.4
        - PHP 5.3
    validations:
      required: true

  - type: textarea
    id: deploy-src
    attributes:
      label: Content of deploy.php or deploy.yaml
      description: Please, provide a minimal reproducible example of deploy.php or deploy.yaml file.
    validations:
      required: false

  - type: textarea
    attributes:
      label: Steps to reproduce
      description: Please provide the steps to reproduce the bug.
    validations:
      required: true


================================================
FILE: .github/FUNDING.yml
================================================
github: antonmedv


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Bug Report
    url: https://github.com/deployphp/deployer/discussions/new?category=bugs
    about: Submit a bug or an issue
  - name: Feature request
    url: https://github.com/deployphp/deployer/discussions/new?category=features
    about: For ideas or feature requests
  - name: Support questions & other
    url: https://github.com/deployphp/deployer/discussions/new?category=help-needed
    about: If you have a question or need help using the library
  - name: General discussion
    url: https://github.com/deployphp/deployer/discussions/new?category=general
    about: Start a new discussion


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
- [ ] Bug fix #…?
- [ ] New feature?
- [ ] BC breaks?
- [ ] Tests added?
- [ ] Docs added?

      Please, regenerate docs by running next command:
      $ php bin/docgen


================================================
FILE: .github/labeler.yml
================================================
v7:
- base-branch: "7.x"

================================================
FILE: .github/workflows/check.yml
================================================
name: check

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  phpstan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'

      - name: Validate composer.json and composer.lock
        run: composer validate

      - name: Get Composer Cache Directory
        id: composer-cache
        run: |
          echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

      - uses: actions/cache@v4
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: |
            ${{ runner.os }}-composer-

      - name: Install dependencies
        if: steps.composer-cache.outputs.cache-hit != 'true'
        run: composer install --prefer-dist --no-progress

      - name: Run test suite
        run: composer phpstan

  code-style:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'

      - name: Validate composer.json and composer.lock
        run: composer validate

      - name: Get Composer Cache Directory
        id: composer-cache
        run: |
          echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

      - uses: actions/cache@v4
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: |
            ${{ runner.os }}-composer-

      - name: Install dependencies
        if: steps.composer-cache.outputs.cache-hit != 'true'
        run: composer install --prefer-dist --no-progress

      - name: Run php-cs-fixer
        run: composer check


================================================
FILE: .github/workflows/docker.yml
================================================
name: docker

on:
  release:
    types: [ published ]
  workflow_dispatch:
    inputs:
      version:
        description: 'Version'
        required: true

permissions:
  id-token: write
  attestations: write

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'

      - name: Get version
        run: |
          echo "RELEASE_VERSION=${GITHUB_REF#refs/*/v}" >> $GITHUB_ENV
          if [ -n "$VERSION" ]; then
            echo "RELEASE_VERSION=${{ inputs.version }}" >> $GITHUB_ENV
          fi
        env:
          VERSION: ${{ inputs.version }}

      - name: Build phar
        run: php -d phar.readonly=0 bin/build -v"$RELEASE_VERSION"

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: deployphp/deployer
          tags: |
            type=raw,value=latest,enable={{is_default_branch}}
            type=sha,format=long
            type=sha
            type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
            type=semver,pattern=v{{major}}.{{minor}}
            type=semver,pattern=v{{major}}
            type=ref,event=tag

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: deployphp
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          provenance: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}


================================================
FILE: .github/workflows/docs-sync.yml
================================================
name: doc-sync

on:
  push:
    branches: [ master ]

permissions:
  contents: write

jobs:
  docgen-and-commit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'

      - name: Get Composer Cache Directory
        id: composer-cache
        run: |
          echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

      - uses: actions/cache@v4
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: |
            ${{ runner.os }}-composer-

      - name: Install dependencies
        if: steps.composer-cache.outputs.cache-hit != 'true'
        run: composer install --prefer-dist --no-progress

      - name: Run docgen
        run: php bin/docgen

      - name: Add & Commit
        uses: EndBug/add-and-commit@v9
        with:
          default_author: github_actions
          add: 'docs'
          message: '[automatic] Update docs with bin/docgen'


================================================
FILE: .github/workflows/docs.yml
================================================
name: doc

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  docgen:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'

      - name: Get Composer Cache Directory
        id: composer-cache
        run: |
          echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

      - uses: actions/cache@v4
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: |
            ${{ runner.os }}-composer-

      - name: Install dependencies
        if: steps.composer-cache.outputs.cache-hit != 'true'
        run: composer install --prefer-dist --no-progress

      - name: Run docgen
        run: php bin/docgen

      - name: Check for uncommitted changes
        run: |
          status=$(git status --porcelain docs/);
          [ -z "$status" ] || {
            echo "Please, run bin/docgen and commit next files:";
            echo $status;
            exit 1;
          }


================================================
FILE: .github/workflows/labeler.yml
================================================
name: labeler

on:
- pull_request_target

jobs:
  labeler:
    permissions:
      contents: read
      pull-requests: write
    runs-on: ubuntu-latest
    steps:
    - uses: actions/labeler@v6
  

================================================
FILE: .github/workflows/lint.yml
================================================
name: lint

on:
  push:
    branches: [ master ]
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]

jobs:
  lint:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        php-versions: [ '8.2' ]
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php-versions }}
          tools: cs2pr, parallel-lint

      - name: Lint sources
        run: composer exec --no-interaction -- parallel-lint bin/ contrib/ recipe/ src/ tests/ --checkstyle | cs2pr


================================================
FILE: .github/workflows/release.yml
================================================
name: release

on:
  release:
    types: [published]
  workflow_dispatch:
    inputs:
      version:
        description: 'Version'
        required: true

permissions:
  contents: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Get version
        run: |
          echo "RELEASE_VERSION=${GITHUB_REF#refs/*/v}" >> $GITHUB_ENV
          if [ -n "$VERSION" ]; then
            echo "RELEASE_VERSION=${{ inputs.version }}" >> $GITHUB_ENV
          fi
        env:
          VERSION: ${{ inputs.version }}

      - name: Build phar
        run: php -d phar.readonly=0 bin/build -v"$RELEASE_VERSION"

      - name: Sign phar
        run: |
          mkdir -p ~/.gnupg/
          chmod 0700 ~/.gnupg/
          echo "$GPG_SIGNING_KEY" > ~/.gnupg/private.key
          gpg --import --no-tty --batch --yes ~/.gnupg/private.key
          gpg -u anton@deployer.org --batch --pinentry-mode loopback --passphrase "${GPG_PASSPHRASE}" --detach-sign --output deployer.phar.asc deployer.phar
        env:
          GPG_SIGNING_KEY: |
            ${{ secrets.GPG_SIGNING_KEY }}
          GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}

      - name: Upload phar
        run: gh release upload v"${RELEASE_VERSION}" deployer.phar
        env:
          GH_TOKEN: ${{ github.token }}

      - name: Upload signature
        run: gh release upload v"${RELEASE_VERSION}" deployer.phar.asc
        env:
          GH_TOKEN: ${{ github.token }}


================================================
FILE: .github/workflows/stale.yml
================================================
name: stale
on:
  schedule:
    - cron: "* * * * *"
  workflow_dispatch:

jobs:
  close-issues:
    runs-on: ubuntu-latest
    permissions:
      issues: write
    steps:
      - uses: actions/stale@v9
        with:
          days-before-issue-stale: 0
          days-before-issue-close: 0
          ignore-updates: true
          close-issue-message: |
            This issue has been automatically closed. Please, open a discussion for bug reports and feature requests.
            
            Read more: https://github.com/deployphp/deployer/discussions/3888
          days-before-pr-stale: -1
          days-before-pr-close: -1
          operations-per-run: 1440


================================================
FILE: .github/workflows/test.yml
================================================
name: test

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  unit:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        php-versions: [ '8.2', '8.3' ]
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php-versions }}
          extensions: mbstring, intl
          coverage: xdebug

      - name: Validate composer.json and composer.lock
        run: composer validate

      - name: Get Composer Cache Directory
        id: composer-cache
        run: |
          echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

      - uses: actions/cache@v4
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: |
            ${{ runner.os }}-composer-

      - name: Install dependencies
        if: steps.composer-cache.outputs.cache-hit != 'true'
        run: composer install --prefer-dist --no-progress

      - name: Run test suite
        run: composer test


================================================
FILE: .gitignore
================================================
/vendor/
*.phar
.phpunit.result.cache
docker-compose.override.yml
.php-cs-fixer.cache
.idea/


================================================
FILE: .php-cs-fixer.dist.php
================================================
<?php

$finder = (new PhpCsFixer\Finder())
    ->in(__DIR__ . '/src')
    ->in(__DIR__ . '/recipe')
    ->in(__DIR__ . '/contrib')
    ->in(__DIR__ . '/tests');

return (new PhpCsFixer\Config())
    ->setRules([
        '@PER-CS' => true,

        // Due to historical reasons we have to keep this.
        // Docs parser expects comment right after php tag.
        'blank_line_after_opening_tag' => false,

        // For PHP 7.4 compatibility.
        'trailing_comma_in_multiline' => [
            'elements' => ['arguments', 'array_destructuring', 'arrays']
        ],
    ])
    ->setFinder($finder);


================================================
FILE: Dockerfile
================================================
FROM php:8.4-cli-alpine

RUN apk add --no-cache bash git openssh-client rsync zip unzip libzip-dev \

RUN docker-php-ext-install mbstring mcrypt pcntl sockets curl zip

COPY --chmod=755 deployer.phar /bin/dep

WORKDIR /app

ENTRYPOINT ["/bin/dep"]


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright © 2013 Anton Medvedev

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.


================================================
FILE: README.md
================================================
<h1>
    <a href="https://deployer.org">
        <picture>
            <source media="(prefers-color-scheme: dark)" srcset="https://deployer.org/img/logo-white.svg" height="30">
            <img src="https://deployer.org/img/logo.svg" alt="Deployer Logo" height="30">
        </picture>
    </a>
    Deployer
</h1>
<p>The PHP deployment tool with support for popular frameworks out of the box.</p>

<p align="center"><br><br><a href="https://deployer.org"><img src="https://medv.io/assets/deployer/deployer.gif" alt="Deployer Screenshot" width="530"></a><br><br><br></p>

---

<p style="font-size: 21px; color:black;">
    Browser testing via<br> 
    <a href="https://www.testmu.ai" target="_blank">
        <picture>
            <source media="(prefers-color-scheme: dark)" srcset=".github/testmu-black-logo.png" width="800">
            <img src=".github/testmu-white-logo.png" alt="Testmu AI" width="800">
        </picture>
    </a>
</p>

---

<a href="https://github.com/deployphp/deployer/actions?query=workflow%3Atest"><img src="https://github.com/deployphp/deployer/workflows/test/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/deployer/deployer"><img src="https://img.shields.io/packagist/v/deployer/deployer.svg?style=flat" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/deployer/deployer"><img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat" alt="License"></a>

See [deployer.org](https://deployer.org) for more information and documentation.

## Features

- Automatic server **provisioning**.
- **Zero downtime** deployments.
- Ready to use recipes for **most frameworks**.

## Additional resources

* [GitHub Action for Deployer](https://github.com/deployphp/action)
* [Deployer Docker Image](https://hub.docker.com/r/deployphp/deployer)

## License
[MIT](https://github.com/deployphp/deployer/blob/master/LICENSE)

<p align="center">
  <a href="https://crow.watch/join/deployer">
    <img src="https://github.com/user-attachments/assets/37c84073-6533-4746-951d-d879f90a7fd2" alt="Join Crow Watch" width="900" hight="600">  
  </a>
</p>


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Supported Versions

Deployer is generally backwards compatible with very few exceptions, so we
recommend users to always use the latest version to experience stability,
performance and security.

We generally backport security issues to a single previous major version,
unless this is not possible or feasible with a reasonable effort.

| Version | Supported          |
|---------|--------------------|
| 8       | :white_check_mark: |
| 7       | :white_check_mark: |
| < 7     | :x:                |

## Reporting a Vulnerability

If you believe you've discovered a serious vulnerability, please contact the
Expr core team at anton+security@medv.io. We will evaluate your report and if
necessary issue a fix and an advisory. If the issue was previously undisclosed,
we'll also mention your name in the credits.


================================================
FILE: bin/build
================================================
#!/usr/bin/env php
<?php
/* (c) Anton Medvedev <anton@medv.io>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
if (ini_get('phar.readonly') === '1') {
    throw new \Exception('Writing to phar files is disabled. Change your `php.ini` or append `-d phar.readonly=false` to the shebang, if supported by your `env` executable.');
}

define('__ROOT__', realpath(__DIR__ . '/..'));
chdir(__ROOT__);

$opt = getopt('v:', ['nozip']);

$version = $opt['v'] ?? null;
if (empty($version)) {
    echo "Please, specify version as \"-v8.0.0\".\n";
    exit(1);
}
if (!preg_match('/^\d+\.\d+\.\d+(\-\w+(\.\d+)?)?$/', $version)) {
    echo "Version must be \"7.0.0-beta.42\". Got \"$version\".\n";
    exit(1);
}

echo `set -x; composer install --no-dev --prefer-dist --optimize-autoloader`;

$pharName = "deployer.phar";
$pharFile = __ROOT__ . '/' . $pharName;
if (file_exists($pharFile)) {
    unlink($pharFile);
}

$ignore = [
    '.anton',
    '.git',
    'Tests',
    'tests',
    'deploy.php',
    '.php-cs-fixer.dist.php',
];

$phar = new \Phar($pharFile, 0, $pharName);
$phar->setSignatureAlgorithm(\Phar::SHA1);
$phar->startBuffering();
$iterator = new RecursiveDirectoryIterator(__ROOT__, FilesystemIterator::SKIP_DOTS);
$iterator = new RecursiveCallbackFilterIterator($iterator, function (SplFileInfo $fileInfo) use ($ignore) {
    return !in_array($fileInfo->getBasename(), $ignore, true);
});
$iterator = new RecursiveIteratorIterator($iterator);
$iterator = new CallbackFilterIterator($iterator, function (SplFileInfo $fileInfo) {
    //'bash', 'fish', 'zsh' is a completion templates
    return in_array($fileInfo->getExtension(), ['php', 'exe', 'bash', 'fish', 'zsh'], true);
});

foreach ($iterator as $fileInfo) {
    $file = str_replace(__ROOT__, '', $fileInfo->getRealPath());
    echo "+ " . $file . "\n";
    $phar->addFile($fileInfo->getRealPath(), $file);

    if (!array_key_exists('nozip', $opt)) {
        $phar[$file]->compress(Phar::GZ);

        if (!$phar[$file]->isCompressed()) {
            echo "Could not compress File: $file\n";
        }
    }
}

// Add Caddyfile
echo "+ /recipe/provision/Caddyfile\n";
$phar->addFile(realpath(__DIR__ . '/../recipe/provision/Caddyfile'), '/recipe/provision/Caddyfile');

// Add 404.html
echo "+ /recipe/provision/404.html\n";
$phar->addFile(realpath(__DIR__ . '/../recipe/provision/404.html'), '/recipe/provision/404.html');

// Add bin/dep file
echo "+ /bin/dep\n";
$depContent = file_get_contents(__ROOT__ . '/bin/dep');
$depContent = str_replace("#!/usr/bin/env php\n", '', $depContent);
$depContent = str_replace('__FILE__', 'str_replace("phar://", "", Phar::running())', $depContent);
$depContent = preg_replace("/run\('.+?'/", "run('$version'", $depContent);
$phar->addFromString('bin/dep', $depContent);

$phar->setStub(
    <<<STUB
#!/usr/bin/env php
<?php
Phar::mapPhar('{$pharName}');
require 'phar://{$pharName}/bin/dep';
__HALT_COMPILER();
STUB
);
$phar->stopBuffering();
unset($phar);

echo "$pharName was created successfully.\n";


================================================
FILE: bin/dep
================================================
#!/usr/bin/env php
<?php
/* (c) Anton Medvedev <anton@medv.io>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

// Check PHP version
if (PHP_VERSION_ID < 80200) {
    fwrite(STDERR, "PHP 8.2 or higher is required.\n");
    exit(1);
}

// Detect deploy.php location
$deployFile = null;
foreach ($argv as $i => $arg) {
    if (preg_match('/^(-f|--file)$/', $arg, $match) && $i + 1 < count($argv)) {
        $deployFile = $argv[$i + 1];
        break;
    }
    if (preg_match('/^--file=(?<file>.+)$/', $arg, $match)) {
        $deployFile = $match['file'];
        break;
    }
    if (preg_match('/^-f=?(?<file>.+)$/', $arg, $match)) {
        $deployFile = $match['file'];
        break;
    }
}
if (!empty($deployFile)) {
    $deployFile = realpath($deployFile);
}
$lookUp = function (string $name): ?string {
    $dir = getcwd();
    for ($i = 0; $i < 10; $i++) {
        $path = "$dir/$name";
        if (is_readable($path)) {
            return $path;
        }
        $dir = dirname($dir);
    }
    return '';
};
if (empty($deployFile)) {
    $deployFile = $lookUp('deploy.php');
}
if (empty($deployFile)) {
    $deployFile = $lookUp('deploy.yaml');
}
if (empty($deployFile)) {
    $deployFile = $lookUp('deploy.yml');
}

// Detect autoload location
$autoload = [
    __DIR__ . '/../vendor/autoload.php', // The dep located at "deployer.phar/bin" or in development.
    __DIR__ . '/../../../autoload.php', // The dep located at "vendor/deployer/deployer/bin".
    __DIR__ . '/../autoload.php', // The dep located at "vendor/bin".
];
$includes = [
    __DIR__ . '/..',
    __DIR__ . '/../../../deployer/deployer',
    __DIR__ . '/../deployer/deployer',
];
$includePath = false;
for ($i = 0; $i < count($autoload); $i++) {
    if (file_exists($autoload[$i]) && is_dir($includes[$i])) {
        require $autoload[$i];
        $includePath = $includes[$i];
        break;
    }
}
if (empty($includePath)) {
    fwrite(STDERR, "Error: The `autoload.php` file not found in:\n");
    for ($i = 0; $i < count($autoload); $i++) {
        $a = file_exists($autoload[$i]) ? 'true' : 'false';
        $b = is_dir($includes[$i]) ? 'true' : 'false';
        fwrite(STDERR, "  - file_exists($autoload[$i]) = $a\n");
        fwrite(STDERR, "    is_dir($includes[$i]) = $b\n");
    }
    exit(1);
}

// Errors to exception
set_error_handler(function ($severity, $message, $filename, $lineno) {
    if (error_reporting() == 0) {
        return;
    }
    if (error_reporting() & $severity) {
        throw new ErrorException($message, 0, $severity, $filename, $lineno);
    }
});

// Enable recipe loading
set_include_path($includePath . PATH_SEPARATOR . get_include_path());

// Deployer constants
define('DEPLOYER', true);
define('DEPLOYER_BIN', __FILE__);
define('DEPLOYER_DEPLOY_FILE', $deployFile);

Deployer\Deployer::run('master', $deployFile);


================================================
FILE: bin/docgen
================================================
#!/usr/bin/env php
<?php
/* (c) Anton Medvedev <anton@medv.io>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Deployer;

use Deployer\Documentation\ApiGen;
use Deployer\Documentation\DocGen;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;

require __DIR__ . '/../vendor/autoload.php';

chdir(realpath(__DIR__ . '/..'));

$input = new ArgvInput();
$output = new ConsoleOutput();
$app = new Application('DocGen', '1.0.0');
$app->setDefaultCommand('all');

$api = function () use ($output) {
    $parser = new ApiGen();
    $parser->parse(file_get_contents(__DIR__ . '/../src/functions.php'));
    $md = $parser->markdown();
    file_put_contents(__DIR__ . '/../docs/api.md', $md);
    $output->writeln('API Reference documentation updated.');
};

$recipes = function () use ($input, $output) {
    $docgen = new DocGen(__DIR__ . '/..');
    $docgen->parse(__DIR__ . '/../recipe');
    $docgen->parse(__DIR__ . '/../contrib');

    if ($input->getOption('json')) {
        echo json_encode($docgen->recipes, JSON_PRETTY_PRINT);
        return;
    }

    $docgen->gen(__DIR__ . '/../docs');
    $output->writeln('Recipes documentation updated.');
};

$app->register('api')->setCode($api);
$app->register('recipes')->setCode($recipes)->addOption('json');
$app->register('all')->setCode(function () use ($recipes, $api) {
    $api();
    $recipes();
    echo `git status`;
})->addOption('json');

$app->run($input, $output);


================================================
FILE: composer.json
================================================
{
    "name": "deployer/deployer",
    "description": "Deployment Tool",
    "license": "MIT",
    "homepage": "https://deployer.org",
    "support": {
        "docs": "https://deployer.org/docs",
        "source": "https://github.com/deployphp/deployer",
        "issues": "https://github.com/deployphp/deployer/issues"
    },
    "authors": [
        {
            "name": "Anton Medvedev",
            "email": "anton@medv.io"
        }
    ],
    "funding": [
        {
            "type": "github",
            "url": "https://github.com/sponsors/antonmedv"
        }
    ],
    "autoload": {
        "psr-4": {
            "Deployer\\": "src/"
        },
        "files": [
            "src/functions.php",
            "src/Support/helpers.php"
        ]
    },
    "scripts": {
        "test": "pest",
        "test:e2e": "pest --config tests/e2e/phpunit-e2e.xml",
        "check": "php-cs-fixer check",
        "fix": "php-cs-fixer fix",
        "phpstan": "phpstan analyse -c phpstan.neon --memory-limit 1G",
        "phpstan:baseline": "@phpstan --generate-baseline tests/phpstan-baseline.neon"
    },
    "bin": [
        "bin/dep"
    ],
    "require": {
        "php": ">=8.2",
        "symfony/console": "^7.2",
        "symfony/process": "^7.2",
        "symfony/yaml": "^7.2"
    },
    "require-dev": {
        "friendsofphp/php-cs-fixer": "^3.68",
        "pestphp/pest": "^3.3",
        "phpstan/phpstan": "^1.4",
        "phpunit/php-code-coverage": "^11.0",
        "phpunit/phpunit": "^11.4"
    },
    "config": {
        "sort-packages": true,
        "process-timeout": 0,
        "allow-plugins": {
            "pestphp/pest-plugin": true,
            "dealerdirect/phpcodesniffer-composer-installer": true
        }
    }
}


================================================
FILE: contrib/bugsnag.php
================================================
<?php
/*

## Configuration

- *bugsnag_api_key* – the API Key associated with the project. Informs Bugsnag which project has been deployed. This is the only required field.
- *bugsnag_provider* – the name of your source control provider. Required when repository is supplied and only for on-premise services.
- *bugsnag_app_version* – the app version of the code you are currently deploying. Only set this if you tag your releases with semantic version numbers and deploy infrequently. (Optional.)

## Usage

Since you should only notify Bugsnag of a successful deployment, the `bugsnag:notify` task should be executed right at the end.

```php
after('deploy', 'bugsnag:notify');
```
*/

namespace Deployer;

use Deployer\Utility\Httpie;

desc('Notifies Bugsnag of deployment');
task('bugsnag:notify', function () {
    $data = [
        'apiKey'       => get('bugsnag_api_key'),
        'releaseStage' => get('target'),
        'repository'   => get('repository'),
        'provider'     => get('bugsnag_provider', ''),
        'branch'       => get('branch'),
        'revision'     => runLocally('git log -n 1 --format="%h"'),
        'appVersion'   => get('bugsnag_app_version', ''),
    ];

    Httpie::post('https://notify.bugsnag.com/deploy')
        ->jsonBody($data)
        ->send();
});


================================================
FILE: contrib/cachetool.php
================================================
<?php
/*

## Configuration

- **cachetool** *(optional)*: accepts a *string* or an *array* of strings with the unix socket or ip address to php-fpm. If `cachetool` is not given, then the application will look for a configuration file. The file must be named .cachetool.yml or .cachetool.yaml. CacheTool will look for this file on the current directory and in any parent directory until it finds one. If the paths above fail it will try to load /etc/cachetool.yml or /etc/cachetool.yaml configuration file.

    ```php
    set('cachetool', '/var/run/php-fpm.sock');
    // or
    set('cachetool', '127.0.0.1:9000');
    // or
    set('cachetool', ['/var/run/php-fpm.sock', '/var/run/php-fpm-other.sock']);
    ```

You can also specify different cachetool settings for each host:
```php
host('staging')
    ->set('cachetool', '127.0.0.1:9000');

host('production')
    ->set('cachetool', '/var/run/php-fpm.sock');
```

By default, if no `cachetool` parameter is provided, this recipe will fallback to the global setting.

If your deployment user does not have permission to access the php-fpm.sock, you can alternatively use
the web adapter that creates a temporary php file and makes a web request to it with a configuration like
```php
set('cachetool_args', '--web --web-path=./public --web-url=https://{{hostname}}');
```

## Usage

Since APCu and OPcache deal with compiling and caching files, they should be executed right after the symlink is created for the new release:

```php
after('deploy:symlink', 'cachetool:clear:opcache');
// or
after('deploy:symlink', 'cachetool:clear:apcu');
```

## Read more

Read more information about cachetool on the website:
http://gordalina.github.io/cachetool/
 */

namespace Deployer;

set('cachetool', '');
/**
 * URL to download cachetool from if it is not available
 *
 * CacheTool 9.x works with PHP >=8.1
 * CacheTool 8.x works with PHP >=8.0
 * CacheTool 7.x works with PHP >=7.3
 */
set('cachetool_url', 'https://github.com/gordalina/cachetool/releases/download/9.1.0/cachetool.phar');
set('cachetool_args', '');
set('bin/cachetool', function () {
    if (!test('[ -f {{release_or_current_path}}/cachetool.phar ]')) {
        run("cd {{release_or_current_path}} && curl -sLO {{cachetool_url}}");
    }
    return '{{release_or_current_path}}/cachetool.phar';
});
set('cachetool_options', function () {
    $options = (array) get('cachetool');
    $fullOptions = (string) get('cachetool_args');
    $return = [];

    if ($fullOptions !== '') {
        $return = [$fullOptions];
    } elseif (count($options) > 0) {
        foreach ($options as $option) {
            if (is_string($option) && $option !== '') {
                $return[] = "--fcgi={$option}";
            }
        }
    }

    return $return ?: [''];
});

/**
 * Clear opcache cache
 */
desc('Clears OPcode cache');
task('cachetool:clear:opcache', function () {
    $options = get('cachetool_options');
    foreach ($options as $option) {
        run("cd {{release_or_current_path}} && {{bin/php}} {{bin/cachetool}} opcache:reset $option");
    }
});

/**
 * Clear APCu cache
 */
desc('Clears APCu system cache');
task('cachetool:clear:apcu', function () {
    $options = get('cachetool_options');
    foreach ($options as $option) {
        run("cd {{release_or_current_path}} && {{bin/php}} {{bin/cachetool}} apcu:cache:clear $option");
    }
});

/**
 * Clear file status cache, including the realpath cache
 */
desc('Clears file status and realpath caches');
task('cachetool:clear:stat', function () {
    $options = get('cachetool_options');
    foreach ($options as $option) {
        run("cd {{release_or_current_path}} && {{bin/php}} {{bin/cachetool}} stat:clear $option");
    }
});


================================================
FILE: contrib/chatwork.php
================================================
<?php
/*
# Chatwork Recipe

## Installing
  1. Create chatwork account by any manual in the internet
  2. Take chatwork token (Like: b29a700e2d15bef3f26ae6a5c142d1ea) and set `chatwork_token` parameter
  3. Take chatwork room id from url after clicked on the room, and set `chatwork_room_id` parameter
  4. If you want, you can edit `chatwork_notify_text`, `chatwork_success_text` or `chatwork_failure_text`
  5. Require chatwork recipe in your `deploy.php` file

```php
# https://deployer.org/recipes.html

require 'recipe/chatwork.php';
```

Add hook on deploy:

```php
before('deploy', 'chatwork:notify');
```

## Configuration

- `chatwork_token` – chatwork bot token, **required**
- `chatwork_room_id` — chatwork room to push messages to **required**
- `chatwork_notify_text` – notification message template
  ```
  [info]
    [title](*) Deployment Status: Deploying[/title]
    Repo: {{repository}}
    Branch: {{branch}}
    Server: {{hostname}}
    Release Path: {{release_path}}
    Current Path: {{current_path}}
  [/info]
  ```
- `chatwork_success_text` – success template, default:
  ```
  [info]
    [title](*) Deployment Status: Successfully[/title]
    Repo: {{repository}}
    Branch: {{branch}}
    Server: {{hostname}}
    Release Path: {{release_path}}
    Current Path: {{current_path}}
  [/info]"
  ```
- `chatwork_failure_text` – failure template, default:
  ```
  [info]
    [title](*) Deployment Status: Failed[/title]
    Repo: {{repository}}
    Branch: {{branch}}
    Server: {{hostname}}
    Release Path: {{release_path}}
    Current Path: {{current_path}}
  [/info]"
  ```

## Tasks

- `chatwork:notify` – send message to chatwork
- `chatwork:notify:success` – send success message to chatwork
- `chatwork:notify:failure` – send failure message to chatwork

## Usage

If you want to notify only about beginning of deployment add this line only:

```php
before('deploy', 'chatwork:notify');
```

If you want to notify about successful end of deployment add this too:

```php
after('deploy:success', 'chatwork:notify:success');
```
If you want to notify about failed deployment add this too:

```php
after('deploy:failed', 'chatwork:notify:failure');
```
 */

namespace Deployer;

use Deployer\Utility\Httpie;

// Chatwork settings
set('chatwork_token', function () {
    throw new \RuntimeException('Please configure "chatwork_token" parameter.');
});
set('chatwork_room_id', function () {
    throw new \RuntimeException('Please configure "chatwork_room_id" parameter.');
});
set('chatwork_api', function () {
    return 'https://api.chatwork.com/v2/rooms/' . get('chatwork_room_id') . '/messages';
});

// The Messages
set('chatwork_notify_text', "[info]\n[title](*) Deployment Status: Deploying[/title]\nRepo: {{repository}}\nBranch: {{branch}}\nServer: {{hostname}}\nRelease Path: {{release_path}}\nCurrent Path: {{current_path}}\n[/info]");
set('chatwork_success_text', "[info]\n[title](*) Deployment Status: Successfully[/title]\nRepo: {{repository}}\nBranch: {{branch}}\nServer: {{hostname}}\nRelease Path: {{release_path}}\nCurrent Path: {{current_path}}\n[/info]");
set('chatwork_failure_text', "[info]\n[title](*) Deployment Status: Failed[/title]\nRepo: {{repository}}\nBranch: {{branch}}\nServer: {{hostname}}\nRelease Path: {{release_path}}\nCurrent Path: {{current_path}}\n[/info]");

// Helpers
task('chatwork_send_message', function () {
    Httpie::post(get('chatwork_api'))
        ->formBody(['body' => get('chatwork_message')])
        ->header("X-ChatWorkToken", get('chatwork_token'))
        ->send();
});

// Tasks
desc('Tests messages');
task('chatwork:test', function () {
    set('chatwork_message', get('chatwork_notify_text'));
    invoke('chatwork_send_message');
    set('chatwork_message', get('chatwork_success_text'));
    invoke('chatwork_send_message');
    set('chatwork_message', get('chatwork_failure_text'));
    invoke('chatwork_send_message');
})
    ->once();

desc('Notifies Chatwork');
task('chatwork:notify', function () {
    if (!get('chatwork_token', false)) {
        return;
    }

    if (!get('chatwork_room_id', false)) {
        return;
    }
    set('chatwork_message', get('chatwork_notify_text'));
    invoke('chatwork_send_message');
})
    ->once()
    ->hidden();

desc('Notifies Chatwork about deploy finish');
task('chatwork:notify:success', function () {
    if (!get('chatwork_token', false)) {
        return;
    }

    if (!get('chatwork_room_id', false)) {
        return;
    }

    set('chatwork_message', get('chatwork_success_text'));
    invoke('chatwork_send_message');
})
    ->once()
    ->hidden();

desc('Notifies Chatwork about deploy failure');
task('chatwork:notify:failure', function () {
    if (!get('chatwork_token', false)) {
        return;
    }

    if (!get('chatwork_room_id', false)) {
        return;
    }

    set('chatwork_message', get('chatwork_failure_text'));
    invoke('chatwork_send_message');
})
    ->once()
    ->hidden();


================================================
FILE: contrib/cimonitor.php
================================================
<?php
/*
Monitor your deployments on [CIMonitor](https://github.com/CIMonitor/CIMonitor).

![CIMonitorGif](https://www.steefmin.xyz/deployer-example.gif)


Add tasks on deploy:

```php
before('deploy', 'cimonitor:notify');
after('deploy:success', 'cimonitor:notify:success');
after('deploy:failed', 'cimonitor:notify:failure');
```

## Configuration

- `cimonitor_webhook` – CIMonitor server webhook url, **required**
  ```
  set('cimonitor_webhook', 'https://cimonitor.enrise.com/webhook/deployer');
  ```
- `cimonitor_title` – the title of application, default the username\reponame combination from `{{repository}}`
  ```
  set('cimonitor_title', '');
  ```
- `cimonitor_user` – User object with name and email, default gets information from `git config`
  ```
  set('cimonitor_user', function () {
    return [
      'name' => 'John Doe',
      'email' => 'john@enrise.com',
    ];
  });
  ```

Various cimonitor statusses are set, in case you want to change these yourselves. See the [CIMonitor documentation](https://cimonitor.readthedocs.io/en/latest/) for the usages of different states.

## Usage

If you want to notify only about beginning of deployment add this line only:

```php
before('deploy', 'cimonitor:notify');
```

If you want to notify about successful end of deployment add this too:

```php
after('deploy:success', 'cimonitor:notify:success');
```

If you want to notify about failed deployment add this too:

```php
after('deploy:failed', 'cimonitor:notify:failure');
```
 */

namespace Deployer;

use Deployer\Utility\Httpie;

// Title of project based on git repo
set('cimonitor_title', function () {
    $repo = get('repository');
    $pattern = '/\w+\/\w+/';
    return preg_match($pattern, $repo, $titles) ? $titles[0] : $repo;
});
set('cimonitor_user', function () {
    return [
        'name' => runLocally('git config --get user.name'),
        'email' => runLocally('git config --get user.email'),
    ];
});

// CI monitor status states and job states
set('cimonitor_status_info', 'info');
set('cimonitor_status_warning', 'warning');
set('cimonitor_status_error', 'error');
set('cimonitor_status_success', 'success');
set('cimonitor_job_state_info', get('cimonitor_status_info'));
set('cimonitor_job_state_pending', 'pending');
set('cimonitor_job_state_running', 'running');
set('cimonitor_job_state_warning', get('cimonitor_status_warning'));
set('cimonitor_job_state_error', get('cimonitor_status_error'));
set('cimonitor_job_state_success', get('cimonitor_status_success'));

desc('Notifies CIMonitor');
task('cimonitor:notify', function () {
    if (!get('cimonitor_webhook', false)) {
        return;
    }

    $body = [
        'state' => get('cimonitor_status_warning'),
        'branch' => get('branch'),
        'title' => get('cimonitor_title'),
        'user' => get('cimonitor_user'),
        'stages' => [get('stage', '')],
        'jobs' => [
            [
                'name' => 'Deploying...',
                'stage' => '',
                'state' => get('cimonitor_job_state_running'),
            ],
        ],
    ];

    Httpie::post(get('cimonitor_webhook'))->jsonBody($body)->send();
})
    ->once()
    ->hidden();

desc('Notifies CIMonitor about deploy finish');
task('cimonitor:notify:success', function () {
    if (!get('cimonitor_webhook', false)) {
        return;
    }

    $depstage = 'Deployed to ' . get('stage', '');

    $body = [
        'state' => get('cimonitor_status_success'),
        'branch' => get('branch'),
        'title' => get('cimonitor_title'),
        'user' => get('cimonitor_user'),
        'stages' => [$depstage],
        'jobs' => [
            [
                'name' => 'Deploy',
                'stage' => $depstage,
                'state' => get('cimonitor_job_state_success'),
            ],
        ],
    ];

    Httpie::post(get('cimonitor_webhook'))->jsonBody($body)->send();
})
    ->once()
    ->hidden();

desc('Notifies CIMonitor about deploy failure');
task('cimonitor:notify:failure', function () {
    if (!get('cimonitor_webhook', false)) {
        return;
    }

    $body = [
        'state' => get('cimonitor_status_error'),
        'branch' => get('branch'),
        'title' => get('cimonitor_title'),
        'user' => get('cimonitor_user'),
        'stages' => [get('stage', '')],
        'jobs' => [
            [
                'name' => 'Deploy',
                'stage' => '',
                'state' => get('cimonitor_job_state_error'),
            ],
        ],
    ];

    Httpie::post(get('cimonitor_webhook'))->jsonBody($body)->send();
})
    ->once()
    ->hidden();


================================================
FILE: contrib/cloudflare.php
================================================
<?php
/*

### Configuration

- `cloudflare` – array with configuration for cloudflare
    - `service_key` – Cloudflare Service Key. If this is not provided, use api_key and email.
    - `api_key` – Cloudflare API key generated on the "My Account" page.
    - `email` – Cloudflare Email address associated with your account.
    - `api_token` – Cloudflare API Token generated on the "My Account" page.
    - `domain` – The domain you want to clear (optional if zone_id is provided).
    - `zone_id` – Cloudflare Zone ID (optional).

### Usage

Since the website should be built and some load is likely about to be applied to your server, this should be one of,
if not the, last tasks before cleanup

*/

namespace Deployer;

desc('Clears Cloudflare Cache');
task('deploy:cloudflare', function () {

    $config = get('cloudflare', []);

    // validate config and set headers
    if (!empty($config['service_key'])) {
        $headers = [
            'X-Auth-User-Service-Key' => $config['service_key'],
        ];
    } elseif (!empty($config['email']) && !empty($config['api_key'])) {
        $headers = [
            'X-Auth-Key'   => $config['api_key'],
            'X-Auth-Email' => $config['email'],
        ];
    } elseif (!empty($config['api_token'])) {
        $headers = [
            'Authorization' => 'Bearer ' . $config['api_token'],
        ];
    } else {
        throw new \RuntimeException("Set a service key or email / api key");
    }

    $headers['Content-Type'] = 'application/json';

    $makeRequest = function ($url, $opts = []) use ($headers) {
        $ch = curl_init("https://api.cloudflare.com/client/v4/$url");

        $parsedHeaders = [];
        foreach ($headers as $key => $value) {
            $parsedHeaders[] = "$key: $value";
        }

        curl_setopt_array($ch, [
            CURLOPT_HTTPHEADER     => $parsedHeaders,
            CURLOPT_RETURNTRANSFER => true,
        ]);

        curl_setopt_array($ch, $opts);

        $res = curl_exec($ch);

        if (curl_errno($ch)) {
            throw new \RuntimeException("Error making curl request (result: $res)");
        }

        if (PHP_MAJOR_VERSION < 8) {
            curl_close($ch);
        }

        return $res;
    };

    $zoneId = $config['zone_id'];
    if (empty($zoneId)) {
        if (empty($config['domain'])) {
            throw new \RuntimeException("Set a domain");
        }

        // get the mysterious zone id from Cloud Flare
        $zones = json_decode($makeRequest(
            "zones?name={$config['domain']}",
        ), true);

        if (!empty($zones['errors'])) {
            throw new \RuntimeException('Problem with zone data');
        } else {
            $zoneId = current($zones['result'])['id'];
        }
    }

    // make purge request
    $makeRequest(
        "zones/$zoneId/purge_cache",
        [
            CURLOPT_CUSTOMREQUEST => 'DELETE',
            CURLOPT_POSTFIELDS    => json_encode(
                [
                    'purge_everything' => true,
                ],
            ),
        ],
    );
});


================================================
FILE: contrib/cpanel.php
================================================
<?php
/*
### Description
This is a recipe that uses the [cPanel 2 API](https://documentation.cPanel.net/display/DD/Guide+to+cPanel+API+2).

Unfortunately the [UAPI](https://documentation.cPanel.net/display/DD/Guide+to+UAPI) that is recommended does not have support for creating addon domains.
The main idea behind is for staging purposes but I guess you can use it for other interesting concepts.

The idea is, every branch possibly has its own staging domain/subdomain (staging-neat-feature.project.com) and database db_neat-feature_project so it can be tested.
This recipe can make the domain/subdomain and database creation part of the deployment process so you don't have to manually create them through an interface.


### Configuration
The example uses a .env file and Dotenv for configuration, but you can set the parameters as you wish
```
set('cpanel', [
    'host' => getenv('CPANEL_HOST'),
    'port' => getenv('CPANEL_PORT'),
    'username' => getenv('CPANEL_USERNAME'),
    'auth_type' => getenv('CPANEL_AUTH_TYPE'),
    'token' => getenv('CPANEL_TOKEN'),
    'user' => getenv('CPANEL_USER'),
    'db_user' => getenv('CPANEL_DB_USER'),
    'db_user_privileges' => getenv('CPANEL_DB_PRIVILEGES'),
    'timeout' => 500,

    'allowInStage' => ['staging', 'beta', 'alpha'],

    'create_domain_format' => '%s-%s-%s',
    'create_domain_values' => ['staging', 'master', get('application')],
    'subdomain_prefix' => substr(md5(get('application')), 0,4) . '-',
    'subdomain_suffix' => getenv('SUDOMAIN_SUFFIX'),


    'create_db_format' => '%s_%s-%s-%s',
    'create_db_values' => ['apps', 'staging','master', get('application')],

]);
```

- `cpanel` – array with configuration for cPanel
    - `username` – WHM account
    - `user` – cPanel account that you want in charge of the domain
    - `token` – WHM API token
    - `create_domain_format` – Format for name creation of domain
    - `create_domain_values` – The actual value reference for naming
    - `subdomain_prefix` – cPanel has a weird way of dealing with addons and subdomains, you cannot create 2 addons with the same subdomain, so you need to change it in some way, example uses first 4 chars of md5(app_name)
    - `subdomain_suffix` – cPanel has a weird way of dealing with addons and subdomains, so the suffix needs to be your main domain for that account for deletion purposes
    - `addondir` – addon dir is different from the deploy path because cPanel "injects" /home/user/ into the path, so tilde cannot be used
    - `allowInStage` – Define the stages that cPanel recipe actions are allowed in


#### .env file example
```
CPANEL_HOST=xxx.xxx.xxx.xxx
CPANEL_PORT=2087
CPANEL_USERNAME=root
CPANEL_TOKEN=xxxx
CPANEL_USER=xxx
CPANEL_AUTH_TYPE=hash
CPANEL_DB_USER=db_user
CPANEL_DB_PRIVILEGES="ALL PRIVILEGES"
SUDOMAIN_SUFFIX=.mymaindomain.com

```

### Tasks

- `cpanel:createaddondomain` Creates an addon domain
- `cpanel:deleteaddondomain` Removes an addon domain
- `cpanel:createdb` Creates a new database

### Usage

A complete example with configs, staging and deployment

```
<?php

namespace Deployer;
use Dotenv\Dotenv;

require 'vendor/autoload.php';
(Dotenv::create(__DIR__))->load(); // this is used just so an .env file can be used for credentials

require 'cpanel.php';


// Project name
set('application', 'myproject.com');
// Project repository
set('repository', 'git@github.com:myorg/myproject.com');





set('cpanel', [
    'host' => getenv('CPANEL_HOST'),
    'port' => getenv('CPANEL_PORT'),
    'username' => getenv('CPANEL_USERNAME'),
    'auth_type' => getenv('CPANEL_AUTH_TYPE'),
    'token' => getenv('CPANEL_TOKEN'),
    'user' => getenv('CPANEL_USER'),
    'db_user' => getenv('CPANEL_DB_USER'),
    'db_user_privileges' => getenv('CPANEL_DB_PRIVILEGES'),
    'timeout' => 500,
    'allowInStage' => ['staging', 'beta', 'alpha'],

    'create_domain_format' => '%s-%s-%s',
    'create_domain_values' => ['staging', 'master', get('application')],
    'subdomain_prefix' => substr(md5(get('application')), 0,4) . '-',
    'subdomain_suffix' => getenv('SUDOMAIN_SUFFIX'),


    'create_db_format' => '%s_%s-%s-%s',
    'create_db_values' => ['apps', 'staging','master', get('application')],

]);

host('myproject.com')
    ->stage('staging')
    ->set('cpanel_createdb', vsprintf(get('cpanel')['create_db_format'], get('cpanel')['create_db_values']))
    ->set('branch', 'dev-branch')
    ->set('deploy_path',  '~/staging/' . vsprintf(get('cpanel')['create_domain_format'], get('cpanel')['create_domain_values']))
    ->set('addondir',  'staging/' . vsprintf(get('cpanel')['create_domain_format'], get('cpanel')['create_domain_values']));
// Tasks
task('build', function () {
    run('cd {{release_path}} && build');
});

after('deploy:prepare', 'cpanel:createaddondomain');
after('deploy:prepare', 'cpanel:createdb');
```
 */

namespace Deployer;

use Deployer\Task\Context;
use Gufy\CpanelPhp\Cpanel;

/**
 * @return Cpanel
 * @throws Exception\Exception
 */
function getCpanel()
{
    $config = get('cpanel', []);
    $allowInStage = $config['allowInStage'];
    $stage = input()->getArgument('stage');

    if (!class_exists('\Gufy\CpanelPhp\Cpanel')) {
        throw new \RuntimeException("<comment>Please install php package</comment> <info>gufy/cpanel-php</info> <comment>to use CPanel API</comment>");
    }

    if (!in_array($stage, $allowInStage)) {
        throw new \RuntimeException(sprintf("Since it creates addon domains and databases, CPanel recipe is available only in the %s environments", implode($allowInStage)));
    }


    if (!is_array($config) ||
        !isset($config['host']) ||
        !isset($config['port']) ||
        !isset($config['username']) ||
        !isset($config['token']) ||
        !isset($config['user'])) {
        throw new \RuntimeException("<comment>Please configure CPanel config:</comment> <info>set('cpanel', array('host' => 'xxx.xxx.xxx.xxx:', 'port' => 2087 , 'username' => 'root', 'token' => 'asdfasdf', 'cpaneluser' => 'guy'));</info>");
    }

    $cpanel = new Cpanel([
        'host'        =>  'https://' . $config['host'] . ':' . $config['port'],
        'username'    =>  $config['username'],
        'auth_type'   =>  $config['auth_type'],
        'password'    =>  $config['token'],
    ]);

    $cpanel->setTimeout($config['timeout']);

    return $cpanel;
}

function getDomainInfo()
{
    $domain = vsprintf(get('cpanel')['create_domain_format'], get('cpanel')['create_domain_values']);
    $cleanDomain = str_replace(['.', ',', ' ', '/', '-'], '', $domain);
    $subDomain = get('cpanel')['subdomain_prefix'] . $cleanDomain;

    return [
        'domain' => $domain,
        'subDomain' => $subDomain,
        'subDomainWithSuffix' => $subDomain . get('cpanel')['subdomain_suffix'],
    ];
}

desc('Creates database though CPanel API');
task('cpanel:createdb', function () {

    $cpanel = getCPanel();
    $config = get('cpanel', []);
    if (!askConfirmation(sprintf('This will try to create the database %s on the host though CPanel API, ok?', get('cpanel_createdb')), true)) {
        return;
    }

    $createDbDataResult = $cpanel->cpanel('MysqlFE', 'createdb', $config['user'], ['db' => get('cpanel_createdb')]);
    $addPrivilegesDataResult = $cpanel->cpanel('MysqlFE', 'setdbuserprivileges', $config['user'], ['privileges' => $config['db_user_privileges'], 'db' => get('cpanel_createdb'), 'dbuser' => $config['db_user']]);

    $createDbData = json_decode($createDbDataResult, true);
    $addPrivilegesData = json_decode($addPrivilegesDataResult, true);

    if (isset($createDbData['cpanelresult']['error'])) {
        writeln($createDbData['cpanelresult']['error']);
    } else {
        writeln('Successfully created database!');
    }

    if (isset($addPrivilegesData['cpanelresult']['error'])) {
        writeln($addPrivilegesData['cpanelresult']['error']);
    } else {
        writeln('Successfully added privileges to database!');
    }
});

desc('Creates addon domain though CPanel API');
task('cpanel:createaddondomain', function () {
    $cpanel = getCPanel();
    $config = get('cpanel', []);
    $domain = getDomainInfo()['domain'];
    $subDomain = getDomainInfo()['subDomain'];
    if (!askConfirmation(sprintf('This will try to create the addon domain %s and point it to %s and subdomain %s, ok?', $domain, get('addondir'), $subDomain), true)) {
        return;
    }

    writeln(sprintf('Creates addon domain %s and pointing it to %s', $domain, get('addondir')));

    $addAddonDomainResult = $cpanel->cpanel('AddonDomain', 'addaddondomain', $config['user'], ['dir' => get('addondir'), 'newdomain' => $domain, 'subdomain' => $subDomain]);
    $addAddonDomainData = json_decode($addAddonDomainResult, true);

    if (isset($addAddonDomainResult['cpanelresult']['error'])) {
        writeln($addAddonDomainData['cpanelresult']['error']);
    } else {
        writeln('Successfully created addon domain!');
        writeln($addAddonDomainData['cpanelresult']['data'][0]['reason']);
    }
});

desc('Deletes addon domain though CPanel API');
task('cpanel:deleteaddondomain', function () {
    $cpanel = getCPanel();
    $config = get('cpanel', []);
    $domain = getDomainInfo()['domain'];
    $subDomain = getDomainInfo()['subDomain'];
    $subDomainWithSuffix = getDomainInfo()['subDomainWithSuffix'];

    if (!askConfirmation(sprintf('This will delete the addon domain %s with corresponding subdomain %s, ok?', $domain, $subDomain), true)) {
        return;
    }

    writeln(sprintf('Deleting addon domain %s', $domain));

    $delAddonDomainResult = $cpanel->cpanel('AddonDomain', 'deladdondomain', $config['user'], ['domain' => $domain, 'subdomain' => $subDomainWithSuffix]);
    $delAddonDomainResult = json_decode($delAddonDomainResult, true);

    if (isset($delAddonDomainResult['cpanelresult']['error'])) {
        writeln($delAddonDomainResult['cpanelresult']['error']);
    } else {
        writeln('Successfully deleted addon domain!');
        writeln($delAddonDomainResult['cpanelresult']['data'][0]['reason']);
    }
});


================================================
FILE: contrib/crontab.php
================================================
<?php
/*
Recipe for adding crontab jobs.

This recipe creates a new section in the crontab file with the configured jobs.
The section is identified by the *crontab:identifier* variable, by default the application name.

## Configuration

- *crontab:jobs* - An array of strings with crontab lines.

## Usage

```php
require 'contrib/crontab.php';

after('deploy:success', 'crontab:sync');

add('crontab:jobs', [
    '* * * * * cd {{current_path}} && {{bin/php}} artisan schedule:run >> /dev/null 2>&1',
]);
```
 */

namespace Deployer;

use function Deployer\Support\escape_shell_argument;

// Get path to bin
set('bin/crontab', function () {
    return which('crontab');
});

// Set the identifier used in the crontab, application name by default
set('crontab:identifier', function () {
    return get('application', 'application');
});

// Use sudo to run crontab. When running crontab with sudo, you can use the `-u` parameter to change a crontab for a different user.
set('crontab:use_sudo', false);

desc('Sync crontab jobs');
task('crontab:sync', function () {
    $cronJobsLocal = array_map(
        fn($job) => parse($job),
        get('crontab:jobs', []),
    );

    if (count($cronJobsLocal) == 0) {
        writeln("Nothing to sync - configure crontab:jobs");
        return;
    }

    $cronJobs = getRemoteCrontab();
    $identifier = get('crontab:identifier');
    $sectionStart = "###< $identifier";
    $sectionEnd = "###> $identifier";

    // find our cronjob section
    $start = array_search($sectionStart, $cronJobs);
    $end = array_search($sectionEnd, $cronJobs);

    if ($start === false || $end === false) {
        // Move the duplicates over when first generating the section
        foreach ($cronJobs as $index => $cronJob) {
            if (in_array($cronJob, $cronJobsLocal)) {
                unset($cronJobs[$index]);
                writeln("Crontab: Found existing job in crontab, moving it to the section");
            }
        }

        // Create the section
        $cronJobs[] = $sectionStart;
        $cronJobs = [...$cronJobs, ...$cronJobsLocal];
        $cronJobs[] = $sectionEnd;
        writeln("Crontab: Found no section, created the section with configured jobs");
    } else {
        // Replace the existing section
        array_splice($cronJobs, $start + 1, $end - $start - 1, $cronJobsLocal);
        writeln("Crontab: Found existing section, replaced with configured jobs");
    }

    setRemoteCrontab($cronJobs);
});

desc('Remove crontab jobs');
task('crontab:remove', function () {
    $cronJobsLocal = array_map(
        fn($job) => parse($job),
        get('crontab:jobs', []),
    );

    $cronJobs = getRemoteCrontab();
    $identifier = get('crontab:identifier');
    $sectionStart = "###< $identifier";
    $sectionEnd = "###> $identifier";

    // Find our cronjob section
    $start = array_search($sectionStart, $cronJobs);
    $end = array_search($sectionEnd, $cronJobs);

    if ($start && $end) {
        // Remove the existing section
        array_splice($cronJobs, $start + 1, $end - $start - 1);
        writeln("Crontab: Found existing section, removed jobs");
    } elseif (count($cronJobsLocal) > 0) {
        $foundJobs = false;
        // Remove individual jobs if no section is present
        foreach ($cronJobs as $index => $cronJob) {
            if (in_array($cronJob, $cronJobsLocal)) {
                unset($cronJobs[$index]);
                $foundJobs = true;
            }
        }
        if ($foundJobs) {
            writeln("Crontab: Found existing jobs in crontab, removed jobs");
        } else {
            writeln("Crontab: No existing jobs in crontab, skipping");
            return;
        }
    } else {
        writeln("Crontab: Found no section and crontab:jobs is not configured, skipping");
        return;
    }

    setRemoteCrontab($cronJobs);
});

function setRemoteCrontab(array $lines): void
{
    $sudo = get('crontab:use_sudo') ? 'sudo' : '';

    $tmpCrontabPath = sprintf('/tmp/%s', uniqid('crontab_save_'));

    if (test("[ -f '$tmpCrontabPath' ]")) {
        run("unlink '$tmpCrontabPath'");
    }

    foreach ($lines as $line) {
        run("echo " . escape_shell_argument($line) . " >> $tmpCrontabPath");
    }

    run("$sudo {{bin/crontab}} " . $tmpCrontabPath);
    run('unlink ' . $tmpCrontabPath);
}

function getRemoteCrontab(): array
{
    $sudo = get('crontab:use_sudo') ? 'sudo' : '';

    if (!test("$sudo {{bin/crontab}} -l >> /dev/null 2>&1")) {
        return [];
    }

    return explode(PHP_EOL, run("$sudo {{bin/crontab}} -l"));
}


================================================
FILE: contrib/directadmin.php
================================================
<?php
/*
### Configuration
- `directadmin` – array with configuration for DirectAdmin
    - `host` – DirectAdmin host
    - `port` – DirectAdmin port (default: 2222, not required)
    - `scheme` – DirectAdmin scheme (default: http, not required)
    - `username` – DirectAdmin username
    - `password` – DirectAdmin password (it is recommended to use login keys!)
    - `db_user` – Database username (required when using directadmin:createdb or directadmin:deletedb)
    - `db_name` – Database namse (required when using directadmin:createdb)
    - `db_password` – Database password (required when using directadmin:createdb)
    - `domain_name` – Domain to create, delete or edit (required when using directadmin:createdomain, directadmin:deletedomain, directadmin:symlink-private-html or directadmin:php-version)
    - `domain_ssl` – Enable SSL, options: ON/OFF, default: ON (optional when using directadmin:createdb)
    - `domain_cgi` – Enable CGI, options: ON/OFF, default: ON (optional when using directadmin:createdb)
    - `domain_php` – Enable PHP, options: ON/OFF, default: ON (optional when using directadmin:createdb)
    - `domain_php_version` – Domain PHP Version, default: 1 (required when using directadmin:php-version)

 */

namespace Deployer;

use Deployer\Task\Context;
use Deployer\Utility\Httpie;

/**
 * getDirectAdminConfig
 *
 * @return array
 */
function getDirectAdminConfig()
{
    $config = get('directadmin', []);

    if (!is_array($config) ||
        !isset($config['host']) ||
        !isset($config['username']) ||
        !isset($config['password'])) {
        throw new \RuntimeException("Please set the following DirectAdmin config:" . PHP_EOL . "set('directadmin', ['host' => '127.0.0.1', 'port' => 2222, 'username' => 'admin', 'password' => 'password']);");
    }

    return $config;
}

/**
 * DirectAdmin
 *
 * @param string $action
 * @param array $data
 *
 * @return void
 */
function DirectAdmin(string $action, array $data = [])
{
    $config = getDirectAdminConfig();
    $scheme = $config['scheme'] ?? 'http';
    $port = $config['port'] ?? 2222;

    $result = Httpie::post(sprintf('%s://%s:%s/%s', $scheme, $config['host'], $port, $action))
        ->formBody($data)
        ->setopt(CURLOPT_USERPWD, $config['username'] . ':' . $config['password'])
        ->send();

    parse_str($result, $resultData);

    if ($resultData['error'] === '1') {
        $resultData['details'] = trim($resultData['details']);
        $resultData['details'] = str_replace(['\\n', '\\r'], '', $resultData['details']);
        $resultData['details'] = strip_tags($resultData['details']);

        writeln('<error>DirectAdmin message: ' . $resultData['details'] . '</error>');
    }
}

desc('Creates a database on DirectAdmin');
task('directadmin:createdb', function () {
    $config = getDirectAdminConfig();

    if (!is_array($config) ||
        !isset($config['db_name']) ||
        !isset($config['db_user']) ||
        !isset($config['db_password'])) {
        throw new \RuntimeException("Please add the following DirectAdmin config:" . PHP_EOL . "add('directadmin', ['db_name' => 'test', 'db_user' => 'test', 'db_password' => '123456']);");
    }

    DirectAdmin('CMD_API_DATABASES', [
        'action' => 'create',
        'name' => $config['db_name'],
        'user' => $config['db_user'],
        'passwd' => $config['db_password'],
        'passwd2' => $config['db_password'],
    ]);
});

desc('Deletes a database on DirectAdmin');
task('directadmin:deletedb', function () {
    $config = getDirectAdminConfig();

    if (!is_array($config) ||
        !isset($config['db_user'])) {
        throw new \RuntimeException("Please add the following DirectAdmin config:" . PHP_EOL . "add('directadmin', ['db_user' => 'test_database']);");
    }

    DirectAdmin('CMD_API_DATABASES', [
        'action' => 'delete',
        'select0' => $config['username'] . '_' . $config['db_user'],
    ]);
});

desc('Creates a domain on DirectAdmin');
task('directadmin:createdomain', function () {
    $config = getDirectAdminConfig();

    if (!is_array($config) ||
        !isset($config['domain_name'])) {
        throw new \RuntimeException("Please add the following DirectAdmin config:" . PHP_EOL . "add('directadmin', ['domain_name' => 'test.example.com']);");
    }

    DirectAdmin('CMD_API_DOMAIN', [
        'action' => 'create',
        'domain' => $config['domain_name'],
        'ssl' => $config['domain_ssl'] ?? 'On',
        'cgi' => $config['domain_cgi'] ?? 'ON',
        'php' => $config['domain_php'] ?? 'ON',
    ]);
});

desc('Deletes a domain on DirectAdmin');
task('directadmin:deletedomain', function () {
    $config = getDirectAdminConfig();

    if (!is_array($config) ||
        !isset($config['domain_name'])) {
        throw new \RuntimeException("Please add the following DirectAdmin config:" . PHP_EOL . "add('directadmin', ['domain_name' => 'test.example.com']);");
    }

    DirectAdmin('CMD_API_DOMAIN', [
        'delete' => 'anything',
        'confirmed' => 'anything',
        'select0' => $config['domain_name'],
    ]);
});

desc('Symlink your private_html to public_html');
task('directadmin:symlink-private-html', function () {
    $config = getDirectAdminConfig();

    if (!is_array($config) ||
        !isset($config['domain_name'])) {
        throw new \RuntimeException("Please add the following DirectAdmin config:" . PHP_EOL . "add('directadmin', ['domain_name' => 'test.example.com']);");
    }

    DirectAdmin('CMD_API_DOMAIN', [
        'action' => 'private_html',
        'domain' => $config['domain_name'],
        'val' => 'symlink',
    ]);
});

desc('Changes the PHP version from a domain');
task('directadmin:php-version', function () {
    $config = getDirectAdminConfig();

    if (!is_array($config) ||
        !isset($config['domain_name']) ||
        !isset($config['domain_php_version'])) {
        throw new \RuntimeException("Please add the following DirectAdmin config:" . PHP_EOL . "add('directadmin', ['domain_name' => 'test.example.com', 'domain_php_version' => 1]);");
    }

    DirectAdmin('CMD_API_DOMAIN', [
        'action' => 'php_selector',
        'domain' => $config['domain_name'],
        'php1_select' => $config['domain_php_version'],
    ]);
});


================================================
FILE: contrib/discord.php
================================================
<?php
/*
## Installing

Add hook on deploy:

```php
before('deploy', 'discord:notify');
```

## Configuration

- `discord_channel` – Discord channel ID, **required**
- `discord_token` – Discord channel token, **required**

- `discord_notify_text` – notification message template, markdown supported, default:
  ```markdown
  :information_source: **{{user}}** is deploying branch `{{branch}}` to _{{where}}_
  ```
- `discord_success_text` – success template, default:
  ```markdown
  :white_check_mark: Branch `{{branch}}` deployed to _{{where}}_ successfully
  ```
- `discord_failure_text` – failure template, default:
  ```markdown
  :no_entry_sign: Branch `{{branch}}` has failed to deploy to _{{where}}_

## Usage

If you want to notify only about beginning of deployment add this line only:

```php
before('deploy', 'discord:notify');
```

If you want to notify about successful end of deployment add this too:

```php
after('deploy:success', 'discord:notify:success');
```

If you want to notify about failed deployment add this too:

```php
after('deploy:failed', 'discord:notify:failure');
```
 */

namespace Deployer;

use Deployer\Task\Context;
use Deployer\Utility\Httpie;

set('discord_webhook', function () {
    return 'https://discordapp.com/api/webhooks/{{discord_channel}}/{{discord_token}}/slack';
});

// Deploy messages
set('discord_notify_text', function () {
    return [
        'text' => parse(':information_source: **{{user}}** is deploying branch `{{what}}` to _{{where}}_'),
    ];
});
set('discord_success_text', function () {
    return [
        'text' => parse(':white_check_mark: Branch `{{what}}` deployed to _{{where}}_ successfully'),
    ];
});
set('discord_failure_text', function () {
    return [
        'text' => parse(':no_entry_sign: Branch `{{what}}` has failed to deploy to _{{where}}_'),
    ];
});

// The message
set('discord_message', 'discord_notify_text');

// Helpers
task('discord_send_message', function () {
    $message = get(get('discord_message'));

    Httpie::post(get('discord_webhook'))->jsonBody($message)->send();
});

// Tasks
desc('Tests messages');
task('discord:test', function () {
    set('discord_message', 'discord_notify_text');
    invoke('discord_send_message');
    set('discord_message', 'discord_success_text');
    invoke('discord_send_message');
    set('discord_message', 'discord_failure_text');
    invoke('discord_send_message');
})
    ->once();

desc('Notifies Discord');
task('discord:notify', function () {
    set('discord_message', 'discord_notify_text');
    invoke('discord_send_message');
})
    ->once()
    ->isHidden();

desc('Notifies Discord about deploy finish');
task('discord:notify:success', function () {
    set('discord_message', 'discord_success_text');
    invoke('discord_send_message');
})
    ->once()
    ->isHidden();

desc('Notifies Discord about deploy failure');
task('discord:notify:failure', function () {
    set('discord_message', 'discord_failure_text');
    invoke('discord_send_message');
})
    ->once()
    ->isHidden();


================================================
FILE: contrib/grafana.php
================================================
<?php
/*

## Configuration options

- **url** *(required)*: the URL to the creates annotation api endpoint.
- **token** *(required)*: authentication token. Can be created at Grafana Console.
- **time** *(optional)* – set deploy time of annotation. specify epoch milliseconds. (Defaults is set to the current time in epoch milliseconds.)
- **tags** *(optional)* – set tag of annotation.
- **text** *(optional)* – set text of annotation. (Defaults is set to "Deployed " + git log -n 1 --format="%h")

```php
// deploy.php

set('grafana', [
    'token' => 'eyJrIj...',
    'url' => 'http://grafana/api/annotations',
    'tags' => ['deploy', 'production'],
]);

```

## Usage

If you want to create annotation about successful end of deployment.

```php
after('deploy:success', 'grafana:annotation');
```

*/

namespace Deployer;

use Deployer\Utility\Httpie;

desc('Creates Grafana annotation of deployment');
task('grafana:annotation', function () {
    $defaultConfig = [
        'url' => null,
        'token' => null,
        'time' => round(microtime(true) * 1000),
        'tags' => [],
        'text' => null,
    ];

    $config = array_merge($defaultConfig, (array) get('grafana'));
    if (!is_array($config) || !isset($config['url']) || !isset($config['token'])) {
        throw new \RuntimeException("Please configure Grafana: set('grafana', ['url' => 'https://localhost/api/annotations', token' => 'eyJrIjo...']);");
    }

    $params = [
        'time' => $config['time'],
        'isRegion' => false,
        'tags' => $config['tags'],
        'text' => $config['text'],
    ];
    if (!isset($params['text'])) {
        $params['text'] = 'Deployed ' . trim(runLocally('git log -n 1 --format="%h"'));
    }

    Httpie::post($config['url'])
        ->header('Authorization', 'Bearer ' . $config['token'])
        ->jsonBody($params)
        ->send();
});


================================================
FILE: contrib/hangouts.php
================================================
<?php
/*

Add hook on deploy:

```php
before('deploy', 'chat:notify');
```

## Configuration

- `chat_webhook` – chat incoming webhook url, **required**
- `chat_title` – the title of your notification card, default `{{application}}`
- `chat_subtitle` – the subtitle of your card, default `{{hostname}}`
- `chat_favicon` – an image for the header of your card, default `http://{{hostname}}/favicon.png`
- `chat_line1` – first line of the text in your card, default: `{{branch}}`
- `chat_line2` – second line of the text in your card, default: `{{stage}}`

## Usage

If you want to notify only about beginning of deployment add this line only:

```php
before('deploy', 'chat:notify');
```

If you want to notify about successful end of deployment add this too:

```php
after('deploy:success', 'chat:notify:success');
```

If you want to notify about failed deployment add this too:

```php
after('deploy:failed', 'chat:notify:failure');
```

 */

namespace Deployer;

use Deployer\Utility\Httpie;

// Title of project
set('chat_title', function () {
    return get('application', 'Project');
});

set('chat_subtitle', get('hostname'));

// If 'favicon' is set Google Hangouts Chat will decorate your card with an image.
set('favicon', 'http://{{hostname}}/favicon.png');

// Deploy messages
set('chat_line1', '{{branch}}');
set('chat_line2', '{{stage}}');

desc('Notifies Google Hangouts Chat');
task('chat:notify', function () {
    if (!get('chat_webhook', false)) {
        return;
    }

    $card = [
        'header' => [
            'title' => get('chat_title'),
            'subtitle' => get('chat_subtitle'),
            'imageUrl' => get('favicon'),
            'imageStyle' => 'IMAGE',
        ],
        'sections' => [
            'widgets' => [
                'keyValue' => [
                    'topLabel' => get('chat_line1'),
                    'content' => get('chat_line2'),
                    'contentMultiline' => false,
                    'bottomLabel' => 'started',
                    // Use 'iconUrl' to set a custom icon URL (png)
                    'icon' => 'CLOCK',
                    'button' => [
                        'textButton' => [
                            'text' => 'Visit site',
                            'onClick' => [
                                'openLink' => [
                                    'url' => get('hostname'),
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ];

    Httpie::post(get('chat_webhook'))->jsonBody(['cards' => $card])->send();
})
    ->once()
    ->hidden();

desc('Notifies Google Hangouts Chat about deploy finish');
task('chat:notify:success', function () {
    if (!get('chat_webhook', false)) {
        return;
    }

    $card = [
        'header' => [
            'title' => get('chat_title'),
            'subtitle' => get('chat_subtitle'),
            'imageUrl' => get('favicon'),
            'imageStyle' => 'IMAGE',
        ],
        'sections' => [
            'widgets' => [
                'keyValue' => [
                    'topLabel' => get('chat_line1'),
                    'content' => get('chat_line2'),
                    'contentMultiline' => false,
                    'bottomLabel' => 'succeeded',
                    // Use 'iconUrl' to set a custom icon URL (png)
                    'icon' => 'STAR',
                    'button' => [
                        'textButton' => [
                            'text' => 'Visit site',
                            'onClick' => [
                                'openLink' => [
                                    'url' => get('hostname'),
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ];

    Httpie::post(get('chat_webhook'))->jsonBody(['cards' => $card])->send();
})
    ->once()
    ->hidden();

desc('Notifies Google Hangouts Chat about deploy failure');
task('chat:notify:failure', function () {
    if (!get('chat_webhook', false)) {
        return;
    }

    $card = [
        'header' => [
            'title' => get('chat_title'),
            'subtitle' => get('chat_subtitle'),
            'imageUrl' => get('favicon'),
            'imageStyle' => 'IMAGE',
        ],
        'sections' => [
            'widgets' => [
                'keyValue' => [
                    'topLabel' => get('chat_line1'),
                    'content' => get('chat_line2'),
                    'contentMultiline' => false,
                    'bottomLabel' => 'failed',
                    // Use 'iconUrl' to set a custom icon URL (png)
                    // or use 'icon' and pick from this list:
                    // https://developers.google.com/hangouts/chat/reference/message-formats/cards#customicons
                    'button' => [
                        'textButton' => [
                            'text' => 'Visit site',
                            'onClick' => [
                                'openLink' => [
                                    'url' => get('hostname'),
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ];

    Httpie::post(get('chat_webhook'))->jsonBody(['cards' => $card])->send();
})
    ->once()
    ->hidden();


================================================
FILE: contrib/hipchat.php
================================================
<?php
/*
## Configuration

- `hipchat_token` – Hipchat V1 auth token
- `hipchat_room_id` – Room ID or name
- `hipchat_message` –  Deploy message, default is `_{{user}}_ deploying `{{what}}` to *{{where}}*`
- `hipchat_from` – Default to target
- `hipchat_color` – Message color, default is **green**
- `hipchat_url` –  The URL to the message endpoint, default is https://api.hipchat.com/v1/rooms/message

## Usage

Since you should only notify Hipchat room of a successful deployment, the `hipchat:notify` task should be executed right at the end.

```php
after('deploy', 'hipchat:notify');
```

 */

namespace Deployer;

use Deployer\Utility\Httpie;

set('hipchat_color', 'green');
set('hipchat_from', '{{where}}');
set('hipchat_message', '_{{user}}_ deploying `{{what}}` to *{{where}}*');
set('hipchat_url', 'https://api.hipchat.com/v1/rooms/message');

desc('Notifies Hipchat channel of deployment');
task('hipchat:notify', function () {
    $params = [
        'room_id' => get('hipchat_room_id'),
        'from' => get('target'),
        'message' => get('hipchat_message'),
        'color' => get('hipchat_color'),
        'auth_token' => get('hipchat_token'),
        'notify' => 0,
        'format' => 'json',
    ];

    Httpie::get(get('hipchat_url'))
        ->query($params)
        ->send();
});


================================================
FILE: contrib/ispmanager.php
================================================
<?php
/*
 * This recipe for work with ISPManager Lite panel by API.
 */

namespace Deployer;

use Deployer\Exception\Exception;
use Deployer\Utility\Httpie;

set('ispmanager_owner', 'www-root');
set('ispmanager_doc_root', '/var/www/' . get('ispmanager_owner') . '/data/');

// ISPManager default configuration
set('ispmanager', [
    'api' => [
        'dsn' => 'https://root:password@localhost:1500/ispmgr',
        'secure' => true,
    ],
    'createDomain' => null,
    'updateDomain' => null,
    'deleteDomain' => null,
    'createDatabase' => null,
    'deleteDatabase' => null,
    'phpSelect' => null,
    'createAlias' => null,
    'deleteAlias' => null,
]);

// Vhost default configuration
set('vhost', [
    'name' => '{{domain}}',
    'php_enable' => 'on',
    'aliases' => 'www.{{domain}}',
    'home' => 'www/{{domain}}',
    'owner' => get('ispmanager_owner'),
    'email' => 'webmaster@{{domain}}',
    'charset' => 'off',
    'dirindex' => 'index.php uploaded.html',
    'ssi' => 'on',
    'php' => 'on',
    'php_mode' => 'php_mode_mod',
    'basedir' => 'on',
    'php_apache_version' => 'native',
    'cgi' => 'off',
    'log_access' => 'on',
    'log_error' => 'on',
]);

// Storage
set('ispmanager_session', '');
set('ispmanager_databases', [
    'servers' => [],
    'hosts' => [],
    'dblist' => [],
]);

set('ispmanager_domains', []);
set('ispmanager_phplist', []);
set('ispmanager_aliaslist', []);

desc('Installs ispmanager');
task('ispmanager:init', function () {
    $config = get('ispmanager');

    if (!is_null($config['createDatabase']) || !is_null($config['deleteDatabase'])) {
        invoke('ispmanager:db-server-list');
        invoke('ispmanager:db-list');
    }

    if (!is_null($config['createDomain']) || !is_null($config['deleteDomain'])) {
        invoke('ispmanager:domain-list');
    }

    if (!is_null($config['phpSelect'])) {
        invoke('ispmanager:domain-list');
        invoke('ispmanager:get-php-list');
    }

    if (!is_null($config['createAlias']) || !is_null($config['deleteAlias'])) {
        invoke('ispmanager:domain-list');
    }
});

desc('Takes database servers list');
task('ispmanager:db-server-list', function () {
    $response = ispmanagerRequest('get', [
        'func' => 'db.server',
    ]);

    $hostList = [];
    $serverList = [];

    if (isset($response['doc']['elem']) && count($response['doc']['elem']) > 0) {
        foreach ($response['doc']['elem'] as $dbServer) {
            $serverList[$dbServer['name']['$']] = [
                'host' => $dbServer['host']['$'],
                'name' => $dbServer['name']['$'],
                'version' => $dbServer['savedver']['$'],
            ];

            if (!strpos($dbServer['host']['$'], ':')) {
                $dbHost = $dbServer['host']['$'] . ':3306';
            } else {
                $dbHost = $dbServer['host']['$'];
            }

            $hostList[$dbHost] = [
                'host' => $dbHost,
                'name' => $dbServer['name']['$'],
                'version' => $dbServer['savedver']['$'],
            ];
        }
    }

    add('ispmanager_databases', [
        'servers' => $serverList,
        'hosts' => $hostList,
    ]);
});

desc('Takes databases list');
task('ispmanager:db-list', function () {
    $response = ispmanagerRequest('get', [
        'func' => 'db',
    ]);

    $dbList = [];
    if (isset($response['doc']['elem']) && count($response['doc']['elem']) > 0) {
        foreach ($response['doc']['elem'] as $db) {
            $dbList[$db['pair']['$']] = [
                'name' => $db['name']['$'],
                'server' => $db['server']['$'],
                'location' => $db['pair']['$'],
            ];
        }
    }

    add('ispmanager_databases', [
        'dblist' => $dbList,
    ]);
});

desc('Takes domain list');
task('ispmanager:domain-list', function () {
    $response = ispmanagerRequest('get', [
        'func' => 'webdomain',
    ]);

    $domainList = [];
    if (isset($response['doc']['elem']) && count($response['doc']['elem']) > 0) {
        foreach ($response['doc']['elem'] as $domain) {
            $domainList[] = $domain['name']['$'];
        }
    }

    add('ispmanager_domains', $domainList);
});

desc('Creates new database');
task('ispmanager:db-create', function () {
    $config = get('ispmanager');

    if (is_null($config['createDatabase'])) {
        warning('Action for database create is not active');
        return;
    }

    $dsnData = parse_url($config['createDatabase']['dsn']);

    $dbInfo = get('ispmanager_databases');

    $hostInfo = null;
    foreach ($dbInfo['hosts'] as $hostData) {
        if ($hostData['host'] == $dsnData['host'] . ':' . $dsnData['port']) {
            $hostInfo = $hostData;
            break;
        }
    }

    if (is_null($hostInfo)) {
        throw new Exception('Incorrect DB host');
    }

    $dbName = substr($dsnData['path'], 1);

    $dbLocation = $dbName . '->' . $hostInfo['name'];

    if (isset($dbInfo['dblist'][$dbLocation])) {
        if (!isset($config['createDatabase']['skipIfExist']) || !$config['createDatabase']['skipIfExist']) {
            throw new Exception('Database already exists!');
        } else {
            warning('Database already exists - skipping');
            return;
        }
    }

    $dbCreateRequest = [
        'func' => 'db.edit',
        'name' => $dbName,
        'owner' => get('ispmanager_owner'),
        'server' => $hostInfo['name'],
        'charset' => $config['createDatabase']['charset'],
        'sok' => 'ok',
    ];

    if ($dsnData['user'] == '*') {
        $dbCreateRequest['user'] = '*';
        $dbCreateRequest['username'] = $dbName;

        if ($dsnData['pass'] == '*') {
            $dbCreateRequest['password'] = generatePassword(8);
        } else {
            $dbCreateRequest['password'] = $dsnData['pass'];
        }
    } else {
        $dbCreateRequest['user'] = $dsnData['user'];
    }


    $response = ispmanagerRequest('post', $dbCreateRequest);

    if (isset($response['doc']['error']['msg']['$'])) {
        throw new Exception($response['doc']['error']['msg']['$']);
    } else {
        info('Database successfully created');
    }
});

desc('Deletes database');
task('ispmanager:db-delete', function () {
    $config = get('ispmanager');

    if (is_null($config['deleteDatabase'])) {
        warning('Action for database delete is not active');
        return;
    }

    $dbInfo = get('ispmanager_databases');
    $dsnData = parse_url($config['deleteDatabase']['dsn']);

    $hostInfo = null;
    foreach ($dbInfo['hosts'] as $hostData) {
        if ($hostData['host'] == $dsnData['host'] . ':' . $dsnData['port']) {
            $hostInfo = $hostData;
            break;
        }
    }

    if (is_null($hostInfo)) {
        throw new Exception('Incorrect DB host');
    }

    $dbName = substr($dsnData['path'], 1);

    $dbLocation = $dbName . '->' . $hostInfo['name'];

    if (!isset($dbInfo['dblist'][$dbLocation])) {
        if (!isset($config['deleteDatabase']['skipIfNotExist']) || !$config['deleteDatabase']['skipIfNotExist']) {
            throw new Exception('Database not exist!');
        } else {
            warning('Database not exist - skipping');
            return;
        }
    }

    $dbDeleteRequest = [
        'func' => 'db.delete',
        'elid' => $dbLocation,
    ];

    $response = ispmanagerRequest('post', $dbDeleteRequest);

    if (isset($response['doc']['error']['msg']['$'])) {
        throw new Exception($response['doc']['error']['msg']['$']);
    } else {
        info('Database successfully deleted');
    }
});

desc('Creates new domain');
task('ispmanager:domain-create', function () {
    $config = get('ispmanager');

    if (is_null($config['createDomain'])) {
        warning('Action for domain create is not active');
        return;
    }

    if (!isset($config['createDomain']['name']) || $config['createDomain']['name'] == '') {
        throw new Exception('Invalid domain name!');
    }

    // Check domain exists
    $existDomains = get('ispmanager_domains');

    if (in_array($config['createDomain']['name'], $existDomains)) {
        if (!isset($config['createDomain']['skipIfExist']) || !$config['createDomain']['skipIfExist']) {
            throw new Exception('Domain already exists!');
        } else {
            warning('Domain already exists - skipping');
            return;
        }
    }

    // Build vhost create request
    $vhostTemplate = get('vhost');

    $domainCreateRequest = [
        'func' => 'webdomain.edit',
        'sok' => 'ok',
    ];

    foreach ($vhostTemplate as $key => $value) {
        $domainCreateRequest[$key] = str_replace('{{domain}}', $config['createDomain']['name'], $vhostTemplate[$key]);
    }

    $response = ispmanagerRequest('post', $domainCreateRequest);

    if (isset($response['doc']['error']['msg']['$'])) {
        throw new Exception($response['doc']['error']['msg']['$']);
    } else {
        info('Domain successfully created');
    }
});

desc('Gets allowed PHP modes and versions');
task('ispmanager:get-php-list', function () {
    // Get www-root settings for fpm version
    $response = ispmanagerRequest('get', [
        'func' => 'user.edit',
        'elid' => get('ispmanager_owner'),
        'elname' => get('ispmanager_owner'),
    ]);

    $userFPMVersion = $response['doc']['limit_php_fpm_version']['$'] ?? null;

    $response = ispmanagerRequest('get', [
        'func' => 'phpversions',
    ]);

    $versions = [];
    foreach ($response['doc']['elem'] as $phpVersion) {
        $versions[$phpVersion['key']['$']] = [
            'name' => $phpVersion['name']['$'],
            'php_mode_mod' => false,
            'php_mode_cgi' => false,
            'php_mode_fcgi_apache' => false,
            'php_mode_fcgi_nginxfpm' => false,
        ];

        if (isset($phpVersion['default_apache']) && $phpVersion['default_apache']['$'] == 'on') {
            $versions[$phpVersion['key']['$']]['php_mode_mod'] = true;
        }

        if (isset($phpVersion['cgi']) && $phpVersion['cgi']['$'] == 'on') {
            $versions[$phpVersion['key']['$']]['php_mode_cgi'] = true;
        }

        if (isset($phpVersion['apache']) && $phpVersion['apache']['$'] == 'on') {
            $versions[$phpVersion['key']['$']]['php_mode_fcgi_apache'] = true;
        }

        if (isset($phpVersion['fpm']) && $phpVersion['fpm']['$'] == 'on' && $phpVersion['key']['$'] == $userFPMVersion) {
            $versions[$phpVersion['key']['$']]['php_mode_fcgi_nginxfpm'] = true;
        }

    }

    add('ispmanager_phplist', $versions);
});

desc('Prints allowed PHP modes and versions');
task('ispmanager:print-php-list', function () {
    invoke('ispmanager:get-php-list');

    $versions = get('ispmanager_phplist');
    writeln("PHP versions: ");
    writeln(str_repeat('*', 32));
    foreach ($versions as $versionKey => $versionData) {
        writeln('PHP ' . $versionData['name'] . ' (ID: ' . $versionKey . ')');
        writeln(str_repeat('*', 32));
        if (!$versionData['php_mode_mod']) {
            writeln('Apache module support (php_mode_mod) - <fg=red;options=bold>NO</>');
        } else {
            writeln('Apache module support (php_mode_mod) - <fg=green;options=bold>YES</>');
        }

        if (!$versionData['php_mode_cgi']) {
            writeln('CGI support (php_mode_cgi) - <fg=red;options=bold>NO</>');
        } else {
            writeln('CGI support (php_mode_cgi) - <fg=green;options=bold>YES</>');
        }

        if (!$versionData['php_mode_fcgi_apache']) {
            writeln('Apache fast-cgi support (php_mode_fcgi_apache) - <fg=red;options=bold>NO</>');
        } else {
            writeln('Apache fast-cgi support (php_mode_fcgi_apache) - <fg=green;options=bold>YES</>');
        }

        if (!$versionData['php_mode_fcgi_nginxfpm']) {
            writeln('nginx fast-cgi support (php_mode_fcgi_nginxfpm) - <fg=red;options=bold>NO</>');
        } else {
            writeln('nginx fast-cgi support (php_mode_fcgi_nginxfpm) - <fg=green;options=bold>YES</>');
        }

        writeln(str_repeat('*', 32));
    }
});

desc('Switches PHP version for domain');
task('ispmanager:domain-php-select', function () {
    $config = get('ispmanager');

    if (is_null($config['phpSelect'])) {
        warning('Action for domain update is not active');
        return;
    }

    if (!isset($config['phpSelect']['name']) || $config['phpSelect']['name'] == '') {
        throw new Exception('Invalid domain name!');
    }

    $existDomains = get('ispmanager_domains');

    if (!in_array($config['phpSelect']['name'], $existDomains)) {
        throw new Exception('Domain not exist!');
    }

    if (!isset($config['phpSelect']['mode']) || !isset($config['phpSelect']['version'])) {
        throw new Exception('Incorrect settings for select php version');
    }

    $phpVersions = get('ispmanager_phplist');

    $newVersion = $config['phpSelect']['version'];
    $newMode = $config['phpSelect']['mode'];

    if (!isset($phpVersions[$newVersion])) {
        throw new Exception('Incorrect php version');
    }

    $versionData = $phpVersions[$newVersion];

    if (!isset($versionData[$newMode]) || !$versionData[$newMode]) {
        throw new Exception('Incorrect php mode');
    }

    $domainUpdateRequest = [
        'func' => 'webdomain.edit',
        'elid' => $config['phpSelect']['name'],
        'name' => $config['phpSelect']['name'],
        'php_mode' => $newMode,
        'sok' => 'ok',
    ];

    if ($newMode == 'php_mode_mod') {
        $domainUpdateRequest['php_apache_version'] = $newVersion;
    } elseif ($newMode == 'php_mode_cgi') {
        $domainUpdateRequest['php_cgi_version'] = $newVersion;
    } elseif ($newMode == 'php_mode_fcgi_apache') {
        $domainUpdateRequest['php_cgi_version'] = $newVersion;
        $domainUpdateRequest['php_apache_version'] = $newVersion;
    } elseif ($newMode == 'php_mode_fcgi_nginxfpm') {
        $domainUpdateRequest['php_cgi_version'] = $newVersion;
        $domainUpdateRequest['php_fpm_version'] = $newVersion;
    } else {
        throw new Exception('Unknown PHP mode!');
    }

    $response = ispmanagerRequest('post', $domainUpdateRequest);

    if (isset($response['doc']['error']['msg']['$'])) {
        throw new Exception($response['doc']['error']['msg']['$']);
    } else {
        info('PHP successfully selected');
    }
});

desc('Creates new domain alias');
task('ispmanager:domain-alias-create', function () {
    $config = get('ispmanager');

    if (is_null($config['createAlias'])) {
        warning('Action for alias create is not active');
        return;
    }

    if (!isset($config['createAlias']['name']) || $config['createAlias']['name'] == '') {
        throw new Exception('Invalid domain name!');
    }

    $existDomains = get('ispmanager_domains');

    if (!in_array($config['createAlias']['name'], $existDomains)) {
        throw new Exception('Domain not exist!');
    }

    if (!isset($config['createAlias']['alias']) || $config['createAlias']['alias'] == '') {
        throw new Exception('Invalid alias name!');
    }

    // Get current domain data
    $response = ispmanagerRequest('get', [
        'func' => 'webdomain.edit',
        'elid' => $config['createAlias']['name'],
        'elname' => $config['createAlias']['name'],
    ]);

    $existAliases = [];
    if (isset($response['doc']['aliases']['$'])) {
        $existAliases = explode(' ', $response['doc']['aliases']['$']);
    }

    $newAliasList = [];
    $createAliasList = explode(' ', $config['createAlias']['alias']);
    foreach ($createAliasList as $createAlias) {
        if (in_array($createAlias, $existAliases)) {
            if (!isset($config['createAlias']['skipIfExist']) || !$config['createAlias']['skipIfExist']) {
                throw new Exception('Alias already exists!');
            } else {
                warning('Alias ' . $createAlias . ' already exists - skipping');
                continue;
            }
        }

        $newAliasList[] = $createAlias;
    }

    $saveAliases = array_merge($existAliases, $newAliasList);

    $domainUpdateRequest = [
        'func' => 'webdomain.edit',
        'elid' => $config['createAlias']['name'],
        'name' => $config['createAlias']['name'],
        'aliases' => implode(' ', $saveAliases),
        'sok' => 'ok',
    ];

    $response = ispmanagerRequest('post', $domainUpdateRequest);

    if (isset($response['doc']['error']['msg']['$'])) {
        throw new Exception($response['doc']['error']['msg']['$']);
    } else {
        info('Alias successfully created');
    }
});

desc('Deletes domain alias');
task('ispmanager:domain-alias-delete', function () {
    $config = get('ispmanager');

    if (is_null($config['deleteAlias'])) {
        warning('Action for alias create is not active');
        return;
    }

    if (!isset($config['deleteAlias']['name']) || $config['deleteAlias']['name'] == '') {
        throw new Exception('Invalid domain name!');
    }

    $existDomains = get('ispmanager_domains');

    if (!in_array($config['deleteAlias']['name'], $existDomains)) {
        throw new Exception('Domain not exist!');
    }

    if (!isset($config['deleteAlias']['alias']) || $config['deleteAlias']['alias'] == '') {
        throw new Exception('Invalid alias name!');
    }

    // Get current domain data
    $response = ispmanagerRequest('get', [
        'func' => 'webdomain.edit',
        'elid' => $config['createAlias']['name'],
        'elname' => $config['createAlias']['name'],
    ]);

    $existAliases = [];
    if (isset($response['doc']['aliases']['$'])) {
        $existAliases = explode(' ', $response['doc']['aliases']['$']);
    }

    $deleteAliasList = explode(' ', $config['deleteAlias']['alias']);
    foreach ($deleteAliasList as $deleteAlias) {
        if (!in_array($deleteAlias, $existAliases)) {
            if (!isset($config['deleteAlias']['skipIfNotExist']) || !$config['deleteAlias']['skipIfNotExist']) {
                throw new Exception('Alias not exist!');
            } else {
                warning('Alias ' . $deleteAlias . ' not exist - skipping');
                continue;
            }
        }

        if (($index = array_search($deleteAlias, $existAliases)) !== false) {
            unset($existAliases[$index]);
        }
    }

    $domainUpdateRequest = [
        'func' => 'webdomain.edit',
        'elid' => $config['deleteAlias']['name'],
        'name' => $config['deleteAlias']['name'],
        'aliases' => implode(' ', $existAliases),
        'sok' => 'ok',
    ];

    $response = ispmanagerRequest('post', $domainUpdateRequest);

    if (isset($response['doc']['error']['msg']['$'])) {
        throw new Exception($response['doc']['error']['msg']['$']);
    } else {
        info('Alias successfully deleted');
    }
});

desc('Deletes domain');
task('ispmanager:domain-delete', function () {
    $config = get('ispmanager');

    if (is_null($config['deleteDomain'])) {
        warning('Action for domain delete is not active');
        return;
    }

    if (!isset($config['deleteDomain']['name']) || $config['deleteDomain']['name'] == '') {
        throw new Exception('Invalid domain name!');
    }

    // Check domain exists
    $existDomains = get('ispmanager_domains');

    if (!in_array($config['deleteDomain']['name'], $existDomains)) {
        if (!isset($config['deleteDomain']['skipIfNotExist']) || !$config['deleteDomain']['skipIfNotExist']) {
            throw new Exception('Domain not exist!');
        } else {
            warning('Domain not exist - skipping');
            return;
        }
    }

    // Build request
    $domainDeleteRequest = [
        'func' => 'webdomain.delete.confirm',
        'elid' => $config['deleteDomain']['name'],
        'sok' => 'ok',
    ];

    if (!isset($config['deleteDomain']['removeDir']) || !$config['deleteDomain']['removeDir']) {
        $domainDeleteRequest['remove_directory'] = 'off';
    } else {
        $domainDeleteRequest['remove_directory'] = 'on';
    }

    $response = ispmanagerRequest('post', $domainDeleteRequest);

    if (isset($response['doc']['error']['msg']['$'])) {
        throw new Exception($response['doc']['error']['msg']['$']);
    } else {
        info('Domain successfully deleted');
    }
});

desc('Auto task processing');
task('ispmanager:process', function () {
    $config = get('ispmanager');

    if (!is_null($config['createDatabase'])) {
        invoke('ispmanager:db-create');
    }

    if (!is_null($config['deleteDatabase'])) {
        invoke('ispmanager:db-delete');
    }

    if (!is_null($config['createDomain'])) {
        invoke('ispmanager:domain-create');
    }

    if (!is_null($config['deleteDomain'])) {
        invoke('ispmanager:domain-delete');
    }

    if (!is_null($config['phpSelect'])) {
        invoke('ispmanager:domain-php-select');
    }

    if (!is_null($config['createAlias'])) {
        invoke('ispmanager:domain-alias-create');
    }

    if (!is_null($config['deleteAlias'])) {
        invoke('ispmanager:domain-alias-delete');
    }
});

function ispmanagerRequest($method, $requestData)
{
    $config = get('ispmanager');
    $dsnData = parse_url($config['api']['dsn']);

    $requestUrl = $dsnData['scheme'] . '://' . $dsnData['host'] . ':' . $dsnData['port'] . $dsnData['path'];

    if ($config['api']['secure'] && get('ispmanager_session') == '') {
        ispmanagerAuthRequest($requestUrl, $dsnData['user'], $dsnData['pass']);
    }

    if ($method == 'post') {
        return Httpie::post($requestUrl)
            ->formBody(prepareRequest($requestData))
            ->setopt(CURLOPT_SSL_VERIFYPEER, false)
            ->setopt(CURLOPT_SSL_VERIFYHOST, false)
            ->getJson();
    } elseif ($method == 'get') {
        return Httpie::get($requestUrl)
            ->query(prepareRequest($requestData))
            ->setopt(CURLOPT_SSL_VERIFYPEER, false)
            ->setopt(CURLOPT_SSL_VERIFYHOST, false)
            ->getJson();
    } else {
        throw new Exception('Unknown request method');
    }
}

function ispmanagerAuthRequest($url, $login, $pass)
{
    $authRequestData = [
        'func' => 'auth',
        'username' => $login,
        'password' => $pass,
    ];

    $responseData = Httpie::post($url)
        ->setopt(CURLOPT_SSL_VERIFYPEER, false)
        ->setopt(CURLOPT_SSL_VERIFYHOST, false)
        ->formBody(prepareRequest($authRequestData))
        ->getJson();

    if (isset($responseData['doc']['auth']['$id'])) {
        set('ispmanager_session', $responseData['doc']['auth']['$id']);
    } else {
        throw new Exception('Error while create auth session');
    }
}

function prepareRequest($requestData)
{
    $config = get('ispmanager');
    $dsnData = parse_url($config['api']['dsn']);

    if (!isset($requestData['out'])) {
        $requestData['out'] = 'json';
    }

    if (!$config['api']['secure']) {
        $requestData['authinfo'] = $dsnData['user'] . ':' . $dsnData['pass'];
    } else {
        if (get('ispmanager_session') != '') {
            $requestData['auth'] = get('ispmanager_session');
        }
    }

    return $requestData;
}

function generatePassword($lenght)
{
    return substr(md5(uniqid()), 0, $lenght);
}

// Callbacks before actions under domains
before('ispmanager:domain-alias-create', 'ispmanager:init');
before('ispmanager:domain-alias-delete', 'ispmanager:init');
before('ispmanager:domain-create', 'ispmanager:init');
before('ispmanager:domain-delete', 'ispmanager:init');
before('ispmanager:domain-php-select', 'ispmanager:init');

// Callbacks before actions under databases
before('ispmanager:db-create', 'ispmanager:init');
before('ispmanager:db-delete', 'ispmanager:init');


================================================
FILE: contrib/mattermost.php
================================================
<?php
/*
## Installing

Create a Mattermost incoming webhook, through the administration panel.

Add hook on deploy:

```
before('deploy', 'mattermost:notify');
```

## Configuration

 - `mattermost_webhook` - incoming mattermost webook **required**
   ```
   set('mattermost_webook', 'https://{your-mattermost-site}/hooks/xxx-generatedkey-xxx');
   ```

 - `mattermost_channel` - overrides the channel the message posts in
   ```
   set('mattermost_channel', 'town-square');
   ```

 - `mattermost_username` - overrides the username the message posts as
   ```
   set('mattermost_username', 'deployer');
   ```

 - `mattermost_icon_url` - overrides the profile picture the message posts with
   ```
   set('mattermost_icon_url', 'https://domain.com/your-icon.png');
   ```

 - `mattermost_text` - notification message
   ```
   set('mattermost_text', '_{{user}}_ deploying `{{what}}` to **{{where}}**');
   ```

 - `mattermost_success_text` – success template, default:
   ```
   set('mattermost_success_text', 'Deploy to **{{where}}** successful {{mattermost_success_emoji}}');
   ```

 - `mattermost_failure_text` – failure template, default:
   ```
   set('mattermost_failure_text', 'Deploy to **{{where}}** failed {{mattermost_failure_emoji}}');
   ```

 - `mattermost_success_emoji` – emoji added at the end of success text
 - `mattermost_failure_emoji` – emoji added at the end of failure text

 For detailed information about Mattermost hooks see: https://developers.mattermost.com/integrate/incoming-webhooks/

## Usage

If you want to notify only about beginning of deployment add this line only:

```php
before('deploy', 'mattermost:notify');
```

If you want to notify about successful end of deployment add this too:

```php
after('deploy:success', 'mattermost:notify:success');
```

If you want to notify about failed deployment add this too:

```php
after('deploy:failed', 'mattermost:notify:failure');
```

 */

namespace Deployer;

use Deployer\Utility\Httpie;

set('mattermost_webhook', null);
set('mattermost_channel', null);
set('mattermost_username', 'deployer');
set('mattermost_icon_url', null);

set('mattermost_success_emoji', ':white_check_mark:');
set('mattermost_failure_emoji', ':x:');

set('mattermost_text', '_{{user}}_ deploying `{{what}}` to **{{where}}**');
set('mattermost_success_text', 'Deploy to **{{where}}** successful {{mattermost_success_emoji}}');
set('mattermost_failure_text', 'Deploy to **{{where}}** failed {{mattermost_failure_emoji}}');

desc('Notifies mattermost');
task('mattermost:notify', function () {
    if (null === get('mattermost_webhook')) {
        return;
    }

    $body = [
        'text' => get('mattermost_text'),
        'username' => get('mattermost_username'),
    ];

    if (get('mattermost_channel')) {
        $body['channel'] = get('mattermost_channel');
    }
    if (get('mattermost_icon_url')) {
        $body['icon_url'] = get('mattermost_icon_url');
    }

    Httpie::post(get('mattermost_webhook'))->jsonBody($body)->send();
});

desc('Notifies mattermost about deploy finish');
task('mattermost:notify:success', function () {
    if (null === get('mattermost_webhook')) {
        return;
    }

    $body = [
        'text' => get('mattermost_success_text'),
        'username' => get('mattermost_username'),
    ];

    if (get('mattermost_channel')) {
        $body['channel'] = get('mattermost_channel');
    }
    if (get('mattermost_icon_url')) {
        $body['icon_url'] = get('mattermost_icon_url');
    }

    Httpie::post(get('mattermost_webhook'))->jsonBody($body)->send();
});

desc('Notifies mattermost about deploy failure');
task('mattermost:notify:failure', function () {
    if (null === get('mattermost_webhook')) {
        return;
    }

    $body = [
        'text' => get('mattermost_failure_text'),
        'username' => get('mattermost_username'),
    ];

    if (get('mattermost_channel')) {
        $body['channel'] = get('mattermost_channel');
    }
    if (get('mattermost_icon_url')) {
        $body['icon_url'] = get('mattermost_icon_url');
    }

    Httpie::post(get('mattermost_webhook'))->jsonBody($body)->send();
});


================================================
FILE: contrib/ms-teams.php
================================================
<?php
/*
## Installing

Require ms-teams recipe in your `deploy.php` file:

Setup:
1. Open MS Teams
2. Navigate to Teams section
3. Select existing or create new team
4. Select existing or create new channel
5. Hover over channel to get three dots, click, in menu select "Connectors"
6. Search for and configure "Incoming Webhook"
7. Confirm/create and copy your Webhook URL
8. Setup deploy.php
    Add in header:
```php
require 'contrib/ms-teams.php';
set('teams_webhook', 'https://outlook.office.com/webhook/...');
```
Add in content:
```php
before('deploy', 'teams:notify');
after('deploy:success', 'teams:notify:success');
after('deploy:failed', 'teams:notify:failure');
```
9.) Sip your coffee

## Configuration

- `teams_webhook` – teams incoming webhook url, **required**
  ```
  set('teams_webhook', 'https://outlook.office.com/webhook/...');
  ```
- `teams_title` – the title of application, default `{{application}}`
- `teams_text` – notification message template, markdown supported
  ```
  set('teams_text', '_{{user}}_ deploying `{{what}}` to *{{where}}*');
  ```
- `teams_success_text` – success template, default:
  ```
  set('teams_success_text', 'Deploy to *{{where}}* successful');
  ```
- `teams_failure_text` – failure template, default:
  ```
  set('teams_failure_text', 'Deploy to *{{where}}* failed');
  ```

- `teams_color` – color's attachment
- `teams_success_color` – success color's attachment
- `teams_failure_color` – failure color's attachment

## Usage

If you want to notify only about beginning of deployment add this line only:

```php
before('deploy', 'teams:notify');
```

If you want to notify about successful end of deployment add this too:

```php
after('deploy:success', 'teams:notify:success');
```

If you want to notify about failed deployment add this too:

```php
after('deploy:failed', 'teams:notify:failure');
```
 */

namespace Deployer;

use Deployer\Utility\Httpie;

// Title of project
set('teams_title', function () {
    return get('application', 'Project');
});

// Allow Continue on Failure
set('teams_failure_continue', false);

// Deploy message
set('teams_text', '_{{user}}_ deploying `{{what}}` to *{{where}}*');
set('teams_success_text', 'Deploy to *{{where}}* successful');
set('teams_failure_text', 'Deploy to *{{where}}* failed');

// Color of attachment
set('teams_color', '#4d91f7');
set('teams_success_color', '#00c100');
set('teams_failure_color', '#ff0909');

desc('Notifies Teams');
task('teams:notify', function () {
    if (!get('teams_webhook', false)) {
        warning('No MS Teams webhook configured');
        return;
    }

    try {
        Httpie::post(get('teams_webhook'))->jsonBody([
            "themeColor" => get('teams_color'),
            'text'       => get('teams_text'),
        ])->send();
    } catch (\Exception $e) {
        if (get('teams_failure_continue', false)) {
            warning('Error sending Teams Notification: ' . $e->getMessage());
        } else {
            throw $e;
        }
    }

})
    ->once()
    ->hidden();

desc('Notifies Teams about deploy finish');
task('teams:notify:success', function () {
    if (!get('teams_webhook', false)) {
        warning('No MS Teams webhook configured');
        return;
    }

    try {
        Httpie::post(get('teams_webhook'))->jsonBody([
            "themeColor" => get('teams_success_color'),
            'text'       => get('teams_success_text'),
        ])->send();
    } catch (\Exception $e) {
        if (get('teams_failure_continue', false)) {
            warning('Error sending Teams Notification: ' . $e->getMessage());
        } else {
            throw $e;
        }
    }
})
    ->once()
    ->hidden();

desc('Notifies Teams about deploy failure');
task('teams:notify:failure', function () {
    if (!get('teams_webhook', false)) {
        warning('No MS Teams webhook configured');
        return;
    }

    try {
        Httpie::post(get('teams_webhook'))->jsonBody([
            "themeColor" => get('teams_failure_color'),
            'text'       => get('teams_failure_text'),
        ])->send();
    } catch (\Exception $e) {
        if (get('teams_failure_continue', false)) {
            warning('Error sending Teams Notification: ' . $e->getMessage());
        } else {
            throw $e;
        }
    }
})
    ->once()
    ->hidden();


================================================
FILE: contrib/newrelic.php
================================================
<?php
/*
## Configuration

- `newrelic_app_id` – newrelic's app id
- `newrelic_api_key` – newrelic's api key
- `newrelic_description` – message to send
- `newrelic_endpoint` – newrelic's REST API endpoint

## Usage

Since you should only notify New Relic of a successful deployment, the `newrelic:notify` task should be executed right at the end.

```php
after('deploy', 'newrelic:notify');
```

 */

namespace Deployer;

use Deployer\Utility\Httpie;

set('newrelic_app_id', function () {
    throw new \Exception('Please, configure "newrelic_app_id" parameter.');
});

set('newrelic_description', function () {
    return runLocally('git log -n 1 --format="%an: %s" | tr \'"\' "\'"');
});

set('newrelic_revision', function () {
    return runLocally('git log -n 1 --format="%h"');
});

set('newrelic_endpoint', 'api.newrelic.com');

desc('Notifies New Relic of deployment');
task('newrelic:notify', function () {
    if (($appId = get('newrelic_app_id')) && ($apiKey = get('newrelic_api_key')) && ($endpoint = get('newrelic_endpoint'))) {
        $data = [
            'user' => get('user'),
            'revision' => get('newrelic_revision'),
            'description' => get('newrelic_description'),
        ];

        Httpie::post("https://$endpoint/v2/applications/$appId/deployments.json")
            ->header("X-Api-Key", $apiKey)
            ->query(['deployment' => $data])
            ->send();
    }
})
    ->once()
    ->hidden();


================================================
FILE: contrib/npm.php
================================================
<?php
/*
## Configuration

- `bin/npm` *(optional)*: set npm binary, automatically detected otherwise.

## Usage

```php
after('deploy:update_code', 'npm:install');
```

 */

namespace Deployer;

set('bin/npm', function () {
    return which('npm');
});

// Uses `npm ci` command. This command is similar to npm install,
// except it's meant to be used in automated environments such as
// test platforms, continuous integration, and deployment -- or
// any situation where you want to make sure you're doing a clean
// install of your dependencies.
desc('Installs npm packages');
task('npm:install', function () {
    run("cd {{release_path}} && {{bin/npm}} ci");
});


================================================
FILE: contrib/ntfy.php
================================================
<?php
/*
## Installing

Require ntfy.sh recipe in your `deploy.php` file:

Setup:
1. Setup deploy.php
    Add in header:
```php
require 'contrib/ntfy.php';
set('ntfy_topic', 'ntfy.sh/mytopic');
```
Add in content:
```php
before('deploy', 'ntfy:notify');
after('deploy:success', 'ntfy:notify:success');
after('deploy:failed', 'ntfy:notify:failure');
```
9.) Sip your coffee

## Configuration

- `ntfy_server` – ntfy server url, default `ntfy.sh`
  ```
  set('ntfy_server', 'ntfy.sh');
  ```
- `ntfy_topic` – ntfy topic, **required**
  ```
  set('ntfy_topic', 'mysecrettopic');
  ```
- `ntfy_title` – the title of the message, default `{{application}}`
- `ntfy_text` – notification message template
  ```
  set('ntfy_text', '_{{user}}_ deploying `{{what}}` to *{{where}}*');
  ```
- `ntfy_tags` – notification message tags / emojis (comma separated)
  ```
  set('ntfy_tags', `information_source`);
  ```
- `ntfy_priority` – notification message priority (integer)
  ```
  set('ntfy_priority', 5);
  ```
- `ntfy_success_text` – success template, default:
  ```
  set('ntfy_success_text', 'Deploy to *{{where}}* successful');
  ```
- `ntfy_success_tags` – success tags / emojis (comma separated)
  ```
  set('ntfy_success_tags', `white_check_mark,champagne`);
  ```
- `ntfy_success_priority` – success notification message priority
- `ntfy_failure_text` – failure template, default:
  ```
  set('ntfy_failure_text', 'Deploy to *{{where}}* failed');
  ```
- `ntfy_failure_tags` – failure tags / emojis (comma separated)
  ```
  set('ntfy_failure_tags', `warning,skull`);
  ```
- `ntfy_failure_priority` – failure notification message priority


## Usage

If you want to notify only about beginning of deployment add this line only:

```php
before('deploy', 'ntfy:notify');
```

If you want to notify about successful end of deployment add this too:

```php
after('deploy:success', 'ntfy:notify:success');
```

If you want to notify about failed deployment add this too:

```php
after('deploy:failed', 'ntfy:notify:failure');
```
 */

namespace Deployer;

use Deployer\Utility\Httpie;

set('ntfy_server', 'ntfy.sh');

// Title of project
set('ntfy_title', function () {
    return get('application', 'Project');
});

// Deploy message
set('ntfy_text', '_{{user}}_ deploying `{{what}}` to *{{where}}*');
set('ntfy_success_text', 'Deploy to *{{where}}* successful');
set('ntfy_failure_text', 'Deploy to *{{where}}* failed');

// Message tags
set('ntfy_tags', '');
set('ntfy_success_tags', '');
set('ntfy_failure_tags', '');

desc('Notifies ntfy server');
task('ntfy:notify', function () {
    if (!get('ntfy_topic', false)) {
        warning('No ntfy topic configured');
        return;
    }

    Httpie::post(get('ntfy_server'))->jsonBody([
        "topic"     => get('ntfy_topic'),
        "title"   => get('ntfy_title'),
        "message"   => get('ntfy_text'),
        "tags"   => explode(",", get('ntfy_tags')),
        "priority"   => get('ntfy_priority'),
    ])->send();
})
    ->once()
    ->hidden();

desc('Notifies ntfy server about deploy finish');
task('ntfy:notify:success', function () {
    if (!get('ntfy_topic', false)) {
        warning('No ntfy topic configured');
        return;
    }

    Httpie::post(get('ntfy_server'))->jsonBody([
        "topic"     => get('ntfy_topic'),
        "title"   => get('ntfy_title'),
        "message"   => get('ntfy_success_text'),
        "tags"   => explode(",", get('ntfy_success_tags')),
        "priority"   => get('ntfy_success_priority'),
    ])->send();
})
    ->once()
    ->hidden();

desc('Notifies ntfy server about deploy failure');
task('ntfy:notify:failure', function () {
    if (!get('ntfy_topic', false)) {
        warning('No ntfy topic configured');
        return;
    }

    Httpie::post(get('ntfy_server'))->jsonBody([
        "topic"     => get('ntfy_topic'),
        "title"   => get('ntfy_title'),
        "message"   => get('ntfy_failure_text'),
        "tags"   => explode(",", get('ntfy_failure_tags')),
        "priority"   => get('ntfy_failure_priority'),
    ])->send();
})
    ->once()
    ->hidden();


================================================
FILE: contrib/phinx.php
================================================
<?php
/*

## Configuration options

All options are in the config parameter `phinx` specified as an array (instead of the `phinx_path` variable).
All parameters are *optional*, but you can specify them with a dictionary (to change all parameters)
or by deployer dot notation (to change one option).

### Phinx params

- `phinx.environment`
- `phinx.date`
- `phinx.configuration` N.B. current directory is the project directory
- `phinx.target`
- `phinx.seed`
- `phinx.parser`
- `phinx.remove-all` (pass empty string as value)

### Phinx path params

- `phinx_path` Specify phinx path (by default phinx is searched for in $PATH, ./vendor/bin and ~/.composer/vendor/bin)

### Example of usage

```php
$phinx_env_vars = [
  'environment' => 'development',
  'configuration' => './migration/.phinx.yml',
  'target' => '20120103083322',
  'remove-all' => '',
];

set('phinx_path', '/usr/local/phinx/bin/phinx');
set('phinx', $phinx_env_vars);

after('cleanup', 'phinx:migrate');

// or set it for a specific server
host('dev')
    ->user('user')
    ->set('deploy_path', '/var/www')
    ->set('phinx', $phinx_env_vars)
    ->set('phinx_path', '');
```

## Suggested Usage

You can run all tasks before or after any
tasks (but you need to specify external configs for phinx).
If you use internal configs (which are in your project) you need
to run it after the `deploy:update_code` task is completed.

## Read more

For further reading see [phinx.org](https://phinx.org). Complete descriptions of all possible options can be found on the [commands page](http://docs.phinx.org/en/latest/commands.html).

 */

namespace Deployer;

use Deployer\Exception\RunException;

/*
 * Phinx recipe for Deployer
 *
 * @author    Alexey Boyko <ket4yiit@gmail.com>
 * @contributor Security-Database <info@security-database.com>
 * @copyright 2016 Alexey Boyko
 * @license   MIT https://github.com/deployphp/recipes/blob/master/LICENSE
 *
 * @link https://github.com/deployphp/recipes
 *
 * @see http://deployer.org
 * @see https://phinx.org
 */

/**
 * Path to Phinx
 */
set('bin/phinx', function () {
    try {
        $phinxPath = which('phinx');
    } catch (RunException $e) {
        $phinxPath = null;
    }

    if ($phinxPath !== null) {
        return "phinx";
    } elseif (test('[ -f {{release_path}}/vendor/bin/phinx ]')) {
        return "{{release_path}}/vendor/bin/phinx";
    } elseif (test('[ -f ~/.composer/vendor/bin/phinx ]')) {
        return '~/.composer/vendor/bin/phinx';
    } else {
        throw new \RuntimeException('Cannot find phinx. Please specify path to phinx manually');
    }
});

/**
 * Make Phinx command
 *
 * @param string $cmdName Name of command
 * @param array $conf Command options(config)
 *
 * @return string Phinx command to execute
 */
function phinx_get_cmd($cmdName, $conf)
{
    $phinx = get('phinx_path') ?: get('bin/phinx');

    $phinxCmd = "$phinx $cmdName";

    $options = '';

    foreach ($conf as $name => $value) {
        $options .= " --$name $value";
    }

    $phinxCmd .= $options;

    return $phinxCmd;
}

/**
 * Returns options array that allowed for command
 *
 * @param array $allowedOptions List of allowed options
 *
 * @return array Array of options
 */
function phinx_get_allowed_config($allowedOptions)
{
    $opts = [];

    try {
        foreach (get('phinx') as $key => $val) {
            if (in_array($key, $allowedOptions)) {
                $opts[$key] = $val;
            }
        }
    } catch (\RuntimeException $e) {
    }

    return $opts;
}


desc('Migrats database with phinx');
task('phinx:migrate', function () {
    $ALLOWED_OPTIONS = [
        'configuration',
        'date',
        'environment',
        'target',
        'parser',
    ];

    $conf = phinx_get_allowed_config($ALLOWED_OPTIONS);

    cd('{{release_path}}');

    $phinxCmd = phinx_get_cmd('migrate', $conf);

    run($phinxCmd);

    cd('{{deploy_path}}');
});

desc('Rollbacks database migrations with phinx');
task('phinx:rollback', function () {
    $ALLOWED_OPTIONS = [
        'configuration',
        'date',
        'environment',
        'target',
        'parser',
    ];

    $conf = phinx_get_allowed_config($ALLOWED_OPTIONS);

    cd('{{release_path}}');

    $phinxCmd = phinx_get_cmd('rollback', $conf);

    run($phinxCmd);

    cd('{{deploy_path}}');
});

desc('Seeds database with phinx');
task('phinx:seed', function () {
    $ALLOWED_OPTIONS = [
        'configuration',
        'environment',
        'parser',
        'seed',
    ];

    $conf = phinx_get_allowed_config($ALLOWED_OPTIONS);

    cd('{{release_path}}');

    $phinxCmd = phinx_get_cmd('seed:run', $conf);

    run($phinxCmd);

    cd('{{deploy_path}}');
});

desc('Sets a migrations breakpoint with phinx');
task('phinx:breakpoint', function () {
    $ALLOWED_OPTIONS = [
        'configuration',
        'environment',
        'remove-all',
        'target',
    ];

    $conf = phinx_get_allowed_config($ALLOWED_OPTIONS);

    cd('{{release_path}}');

    $phinxCmd = phinx_get_cmd('breakpoint', $conf);

    run($phinxCmd);

    cd('{{deploy_path}}');
});


================================================
FILE: contrib/php-fpm.php
================================================
<?php
/*

:::caution
Do **not** reload php-fpm. Some user requests could fail or not complete in the
process of reloading.

Instead, configure your server [properly](avoid-php-fpm-reloading). If you're using Deployer's provision
recipe, it's already configured the right way and no php-fpm reload is needed.
:::

## Configuration

- `php_fpm_version` – The PHP-fpm version. For example: `8.0`.
- `php_fpm_service` – The full name of the PHP-fpm service. Defaults to `php{{php_fpm_version}}-fpm`.
- `php_fpm_command` – The command to run to reload PHP-fpm. Defaults to `sudo systemctl reload {{php_fpm_service}}`.

## Usage

Start by explicitely providing the current version of PHP-version using the `php_fpm_version`.
Alternatively, you may use any of the options above to configure how PHP-fpm should reload.

Then, add the `php-fpm:reload` task at the end of your deployments by using the `after` method like so.

```php
set('php_fpm_version', '8.0');
after('deploy', 'php-fpm:reload');
```

 */

namespace Deployer;

// Automatically detects by using {{bin/php}}.
set('php_fpm_version', function () {
    return run('{{bin/php}} -r "printf(\'%d.%d\', PHP_MAJOR_VERSION, PHP_MINOR_VERSION);"');
});

set('php_fpm_service', 'php{{php_fpm_version}}-fpm');

desc('Reloads the php-fpm service');
task('php-fpm:reload', function () {
    warning('Avoid reloading php-fpm [deployer.org/docs/8.x/avoid-php-fpm-reloading]');
    run('sudo systemctl reload {{php_fpm_service}}');
});


================================================
FILE: contrib/rabbit.php
================================================
<?php
/*
### Installing

```php
// deploy.php

require 'recipe/rabbit.php';
```

### Configuration options

- **rabbit** *(required)*: accepts an *array* with the connection information to [rabbitmq](http://www.rabbitmq.com) server token and team name.


You can provide also other configuration options:

 - *host* - default is localhost
 - *port* - default is 5672
 - *username* - default is *guest*
 - *password* - default is *guest*
 - *channel* - no default value, need to be specified via config
 - *message* - default is **Deployment to '$host' on *$prod* was successful\n$releasePath**
 - *vhost* - default is


```php
// deploy.php

set('rabbit', [
    'host'     => 'localhost',
    'port'     => '5672',
    'username' => 'guest',
    'password' => 'guest',
    'channel'  => 'notify-channel',
    'vhost'    => '/my-app'
]);
```

### Suggested Usage

Since you should only notify RabbitMQ channel of a successful deployment, the `deploy:rabbit` task should be executed right at the end.

```php
// deploy.php

before('deploy:end', 'deploy:rabbit');
```
 */

namespace Deployer;

use Deployer\Task\Context;
use PhpAmqpLib\Connection\AMQPConnection;
use PhpAmqpLib\Message\AMQPMessage;

desc('Notifies RabbitMQ channel about deployment');
task('deploy:rabbit', function () {

    if (!class_exists('PhpAmqpLib\Connection\AMQPConnection')) {
        throw new \RuntimeException("<comment>Please install php package</comment> <info>videlalvaro/php-amqplib</info> <comment>to use rabbitmq</comment>");
    }

    $config = get('rabbit', []);

    if (!isset($config['message'])) {
        $releasePath = get('release_path');
        $host = Context::get()->getHost();

        $stage = get('stage', false);
        $stageInfo = ($stage) ? sprintf(' on *%s*', $stage) : '';

        $message = "Deployment to '%s'%s was successful\n(%s)";
        $config['message'] = sprintf(
            $message,
            $host->getHostname(),
            $stageInfo,
            $releasePath,
        );
    }

    $defaultConfig = [
        'host' => 'localhost',
        'port' => 5672,
        'username' => 'guest',
        'password' => 'guest',
        'vhost' => '/',
    ];

    $config = array_merge($defaultConfig, $config);

    if (!is_array($config) ||
        !isset($config['channel']) ||
        !isset($config['host']) ||
        !isset($config['port']) ||
        !isset($config['username']) ||
        !isset($config['password']) ||
        !isset($config['vhost'])) {
        throw new \RuntimeException("<comment>Please configure rabbit config:</comment> <info>set('rabbit', array('channel' => 'channel', 'host' => 'host', 'port' => 'port', 'username' => 'username', 'password' => 'password'));</info>");
    }

    $connection = new AMQPConnection($config['host'], $config['port'], $config['username'], $config['password'], $config['vhost']);
    $channel = $connection->channel();

    $msg = new AMQPMessage($config['message']);
    $channel->basic_publish($msg, $config['channel'], $config['channel']);

    $channel->close();
    $connection->close();

});


================================================
FILE: contrib/raygun.php
================================================
<?php
/*

## Configuration

- `raygun_api_key` – the API key of your Raygun application
- `raygun_version` – the version of your application that this deployment is releasing
- `raygun_owner_name` – the name of the person creating this deployment
- `raygun_email` – the email of the person creating this deployment
- `raygun_comment` – the deployment notes
- `raygun_scm_identifier` – the commit that this deployment was built off
- `raygun_scm_type` - the source control system you use

## Usage

To notify Raygun of a successful deployment, you can use the 'raygun:notify' task after a deployment.

```php
after('deploy', 'raygun:notify');
```
 */

namespace Deployer;

use Deployer\Utility\Httpie;

desc('Notifies Raygun of deployment');
task('raygun:notify', function () {
    $data = [
        'apiKey'       => get('raygun_api_key'),
        'version' => get('raygun_version'),
        'ownerName'   => get('raygun_owner_name'),
        'emailAddress' => get('raygun_email'),
        'comment' => get('raygun_comment'),
        'scmIdentifier' => get('raygun_scm_identifier'),
        'scmType' => get('raygun_scm_type'),
    ];

    Httpie::post('https://app.raygun.io/deployments')
        ->jsonBody($data)
        ->send();
});


================================================
FILE: contrib/rocketchat.php
================================================
<?php
/*
## Installing

Create a RocketChat incoming webhook, through the administration panel.

Add hook on deploy:

```
before('deploy', 'rocketchat:notify');
```

## Configuration

 - `rocketchat_webhook` - incoming rocketchat webook **required**
   ```
   set('rocketchat_webhook', 'https://rocketchat.yourcompany.com/hooks/XXXXX');
   ```

 - `rocketchat_title` - the title of the application, defaults to `{{application}}`
 - `rocketchat_text` - notification message
   ```
   set('rocketchat_text', '_{{user}}_ deploying {{what}} to {{where}}');
   ```

 - `rocketchat_success_text` – success template, default:
  ```
  set('rocketchat_success_text', 'Deploy to *{{where}}* successful');
  ```
 - `rocketchat_failure_text` – failure template, default:
  ```
  set('rocketchat_failure_text', 'Deploy to *{{where}}* failed');
  ```

 - `rocketchat_color` – color's attachment
 - `rocketchat_success_color` – success color's attachment
 - `rocketchat_failure_color` – failure color's attachment

## Usage

If you want to notify only about beginning of deployment add this line only:

```php
before('deploy', 'rocketchat:notify');
```

If you want to notify about successful end of deployment add this too:

```php
after('deploy:success', 'rocketchat:notify:success');
```

If you want to notify about failed deployment add this too:

```php
after('deploy:failed', 'rocketchat:notify:failure');
```

 */

namespace Deployer;

use Deployer\Utility\Httpie;

set('rockchat_title', function () {
    return get('application', 'Project');
});

set('rocketchat_icon_emoji', ':robot:');
set('rocketchat_icon_url', null);

set('rocketchat_channel', null);
set('rocketchat_room_id', null);
set('rocketchat_username', null);
set('rocketchat_webhook', null);

set('rocketchat_color', '#000000');
set('rocketchat_success_color', '#00c100');
set('rocketchat_failure_color', '#ff0909');

set('rocketchat_text', '_{{user}}_ deploying `{{what}}` to *{{where}}*');
set('rocketchat_success_text', 'Deploy to *{{where}}* successful');
set('rocketchat_failure_text', 'Deploy to *{{where}}* failed');

desc('Notifies RocketChat');
task('rocketchat:notify', function () {
    if (null === get('rocketchat_webhook')) {
        return;
    }

    $body = [
        'text' => get('rockchat_title'),
        'username' => get('rocketchat_username'),
        'attachments' => [[
            'text' => get('rocketchat_text'),
            'color' => get('rocketchat_color'),
        ]],
    ];

    if (get('rocketchat_channel')) {
        $body['channel'] = get('rocketchat_channel');
    }
    if (get('rocketchat_room_id')) {
        $body['roomId'] = get('rocketchat_room_id');
    }
    if (get('rocketchat_icon_url')) {
        $body['avatar'] = get('rocketchat_icon_url');
    } elseif (get('rocketchat_icon_emoji')) {
        $body['emoji'] = get('rocketchat_icon_emoji');
    }

    Httpie::post(get('rocketchat_webhook'))->jsonBody($body)->send();
});

desc('Notifies RocketChat about deploy finish');
task('rocketchat:notify:success', function () {
    if (null === get('rocketchat_webhook')) {
        return;
    }

    $body = [
        'text' => get('rockchat_title'),
        'username' => get('rocketchat_username'),
        'attachments' => [[
            'text' => get('rocketchat_success_text'),
            'color' => get('rocketchat_success_color'),
        ]],
    ];

    if (get('rocketchat_channel')) {
        $body['channel'] = get('rocketchat_channel');
    }
    if (get('rocketchat_room_id')) {
        $body['roomId'] = get('rocketchat_room_id');
    }
    if (get('rocketchat_icon_url')) {
        $body['avatar'] = get('rocketchat_icon_url');
    } elseif (get('rocketchat_icon_emoji')) {
        $body['emoji'] = get('rocketchat_icon_emoji');
    }

    Httpie::post(get('rocketchat_webhook'))->jsonBody($body)->send();
});

desc('Notifies RocketChat about deploy failure');
task('rocketchat:notify:failure', function () {
    if (null === get('rocketchat_webhook')) {
        return;
    }

    $body = [
        'text' => get('rockchat_title'),
        'username' => get('rocketchat_username'),
        'attachments' => [[
            'color' => get('rocketchat_failure_color'),
            'text' => get('rocketchat_failure_text'),
        ]],
    ];

    if (get('rocketchat_channel')) {
        $body['channel'] = get('rocketchat_channel');
    }
    if (get('rocketchat_room_id')) {
        $body['roomId'] = get('rocketchat_room_id');
    }
    if (get('rocketchat_icon_url')) {
        $body['avatar'] = get('rocketchat_icon_url');
    } elseif (get('rocketchat_icon_emoji')) {
        $body['emoji'] = get('rocketchat_icon_emoji');
    }

    Httpie::post(get('rocketchat_webhook'))->jsonBody($body)->send();
});


================================================
FILE: contrib/rollbar.php
================================================
<?php
/*

## Configuration

- `rollbar_token` – access token to rollbar api
- `rollbar_comment` – comment about deploy, default to
  ```php
  set('rollbar_comment', '_{{user}}_ deploying `{{what}}` to *{{where}}*');
  ```
- `rollbar_username` – rollbar user name

## Usage

Since you should only notify Rollbar channel of a successful deployment, the `rollbar:notify` task should be executed right at the end.

```php
after('deploy', 'rollbar:notify');
```

 */

namespace Deployer;

use Deployer\Utility\Httpie;

set('rollbar_comment', '_{{user}}_ deploying `{{what}}` to *{{where}}*');

desc('Notifies Rollbar of deployment');
task('rollbar:notify', function () {
    if (!get('rollbar_token', false)) {
        return;
    }

    $params = [
        'access_token' => get('rollbar_token'),
        'environment' => get('where'),
        'revision' => runLocally('git log -n 1 --format="%h"'),
        'local_username' => get('user'),
        'rollbar_username' => get('rollbar_username'),
        'comment' => get('rollbar_comment'),
    ];

    Httpie::post('https://api.rollbar.com/api/1/deploy/')
        ->formBody($params)
        ->send();
})
    ->once();


================================================
FILE: contrib/rsync.php
================================================
<?php
/*
:::warning
This must not be confused with `/src/Utility/Rsync.php`, deployer's built-in rsync. Their configuration options are also very different, read carefully below.
:::

## Configuration options

- **rsync**: Accepts an array with following rsync options (all are optional and defaults are ok):
    - *exclude*: accepts an *array* with patterns to be excluded from sending to server
    - *exclude-file*: accepts a *string* containing absolute path to file, which contains exclude patterns
    - *include*: accepts an *array* with patterns to be included in sending to server
    - *include-file*: accepts a *string* containing absolute path to file, which contains include patterns
    - *filter*: accepts an *array* of rsync filter rules
    - *filter-file*: accepts a *string* containing merge-file filename.
    - *filter-perdir*: accepts a *string* containing merge-file filename to be scanned and merger per each directory in rsync list on files to send
    - *flags*: accepts a *string* of flags to set when calling rsync command. Please **avoid** flags that accept params, and use *options* instead.
    - *options*: accepts an *array* of options to set when calling rsync command. **DO NOT** prefix options with `--` as it's automatically added.
    - *timeout*: accepts an *int* defining timeout for rsync command to run locally.

### Sample Configuration:

Following is default configuration. By default rsync ignores only git dir and `deploy.php` file.

```php
// deploy.php

set('rsync',[
    'exclude'      => [
        '.git',
        'deploy.php',
    ],
    'exclude-file' => false,
    'include'      => [],
    'include-file' => false,
    'filter'       => [],
    'filter-file'  => false,
    'filter-perdir'=> false,
    'flags'        => 'rz', // Recursive, with compress
    'options'      => ['delete'],
    'timeout'      => 60,
]);
```

If You have multiple excludes, You can put them in file and reference that instead. If You use `deploy:rsync_warmup` You could set additional options that could speed-up and/or affect way things are working. For example:

```php
// deploy.php

set('rsync',[
    'exclude'       => ['excludes_file'],
    'exclude-file'  => '/tmp/localdeploys/excludes_file', //Use absolute path to avoid possible rsync problems
    'include'       => [],
    'include-file'  => false,
    'filter'        => [],
    'filter-file'   => false,
    'filter-perdir' => false,
    'flags'         => 'rzcE', // Recursive, with compress, check based on checksum rather than time/size, preserve Executable flag
    'options'       => ['delete', 'delete-after', 'force'], //Delete after successful transfer, delete even if deleted dir is not empty
    'timeout'       => 3600, //for those huge repos or crappy connection
]);
```


### Parameter

- **rsync_src**: per-host rsync source. This can be server, stage or whatever-dependent. By default it's set to current directory
- **rsync_dest**: per-host rsync destination. This can be server, stage or whatever-dependent. by default it's equivalent to release deploy destination.

### Sample configurations:

This is default configuration:

```php
set('rsync_src', __DIR__);
set('rsync_dest','{{release_path}}');
```

If You use local deploy recipe You can set src to local release:

```php
host('hostname')
    ->hostname('10.10.10.10')
    ->port(22)
    ->set('deploy_path','/your/remote/path/app')
    ->set('rsync_src', '/your/local/path/app')
    ->set('rsync_dest','{{release_path}}');
```

## Usage

- `rsync` task

    Set `rsync_src` to locally cloned repository and rsync to `rsync_dest`. Then set this task instead of `deploy:update_code` in Your `deploy` task if Your hosting provider does not allow git.

- `rsync:warmup` task

    If Your deploy task looks like:

    ```php
    task('deploy', [
        'deploy:prepare',
        'deploy:release',
        'rsync',
        'deploy:vendors',
        'deploy:symlink',
    ])->desc('Deploy your project');
    ```

    And Your `rsync_dest` is set to `{{release_path}}` then You could add this task to run before `rsync` task or after `deploy:release`, whatever is more convenient.

 */

namespace Deployer;

use Deployer\Host\Localhost;
use Deployer\Task\Context;

set('rsync', [
    'exclude' => [
        '.git',
        'deploy.php',
    ],
    'exclude-file' => false,
    'include' => [],
    'include-file' => false,
    'filter' => [],
    'filter-file' => false,
    'filter-perdir' => false,
    'flags' => 'rz',
    'options' => ['delete'],
    'timeout' => 300,
]);

set('rsync_src', __DIR__);
set('rsync_dest', '{{release_path}}');

set('rsync_excludes', function () {
    $config = get('rsync');
    $excludes = $config['exclude'];
    $excludeFile = $config['exclude-file'];
    $excludesRsync = '';
    foreach ($excludes as $exclude) {
        $excludesRsync .= ' --exclude=' . escapeshellarg($exclude);
    }
    if (!empty($excludeFile) && file_exists($excludeFile) && is_file($excludeFile) && is_readable($excludeFile)) {
        $excludesRsync .= ' --exclude-from=' . escapeshellarg($excludeFile);
    }

    return $excludesRsync;
});

set('rsync_includes', function () {
    $config = get('rsync');
    $includes = $config['include'];
    $includeFile = $config['include-file'];
    $includesRsync = '';
    foreach ($includes as $include) {
        $includesRsync .= ' --include=' . escapeshellarg($include);
    }
    if (!empty($includeFile) && file_exists($includeFile) && is_file($includeFile) && is_readable($includeFile)) {
        $includesRsync .= ' --include-from=' . escapeshellarg($includeFile);
    }

    return $includesRsync;
});

set('rsync_filter', function () {
    $config = get('rsync');
    $filters = $config['filter'];
    $filterFile = $config['filter-file'];
    $filterPerDir = $config['filter-perdir'];
    $filtersRsync = '';
    foreach ($filters as $filter) {
        $filtersRsync .= " --filter='$filter'";
    }
    if (!empty($filterFile)) {
        $filtersRsync .= " --filter='merge $filterFile'";
    }
    if (!empty($filterPerDir)) {
        $filtersRsync .= " --filter='dir-merge $filterPerDir'";
    }
    return $filtersRsync;
});

set('rsync_options', function () {
    $config = get('rsync');
    $options = $config['options'];
    $optionsRsync = [];
    foreach ($options as $option) {
        $optionsRsync[] = "--$option";
    }
    return implode(' ', $optionsRsync);
});


desc('Warmups remote Rsync target');
task('rsync:warmup', function () {
    $config = get('rsync');

    $source = "{{current_path}}";
    $destination = "{{deploy_path}}/release";

    if (test("[ -d $(echo $source) ]")) {
        run("rsync -{$config['flags']} {{rsync_options}}{{rsync_excludes}}{{rsync_includes}}{{rsync_filter}} $source/ $destination/");
    } else {
        writeln("<comment>No way to warmup rsync.</comment>");
    }
});


desc('Rsync local->remote');
task('rsync', function () {
    $config = get('rsync');

    $src = get('rsync_src');
    while (is_callable($src)) {
        $src = $src();
    }

    if (!trim($src)) {
        // if $src is not set here rsync is going to do a directory listing
        // exiting with code 0, since only doing a directory listing clearly
        // is not what we want to achieve we need to throw an exception
        throw new \RuntimeException('You need to specify a source path.');
    }

    $dst = get('rsync_dest');
    while (is_callable($dst)) {
        $dst = $dst();
    }

    if (!trim($dst)) {
        // if $dst is not set here we are going to sync to root
        // and even worse - depending on rsync flags and permission -
        // might end up deleting everything we have write permission to
        throw new \RuntimeException('You need to specify a destination path.');
    }

    $rsyncFlags = (is_string($config['flags']) && trim($config['flags']) !== '') ? "-{$config['flags']}" : '';

    $host = Context::get()->getHost();
    if ($host instanceof Localhost) {
        runLocally("rsync {$rsyncFlags} {{rsync_options}}{{rsync_includes}}{{rsync_excludes}}{{rsync_filter}} '$src/' '$dst/'", timeout: $config['timeout']);
        return;
    }

    $sshArguments = $host->connectionOptionsString();
    runLocally("rsync {$rsyncFlags} -e 'ssh $sshArguments' {{rsync_options}}{{rsync_includes}}{{rsync_excludes}}{{rsync_filter}} '$src/' '{$host->connectionString()}:$dst/'", timeout: $config['timeout']);
});


================================================
FILE: contrib/sentry.php
================================================
<?php
/*

### Configuration options

- **organization** *(required)*: the slug of the organization the release belongs to.
- **projects** *(required)*: array of slugs of the projects to create a release for.
- **token** *(required)*: authentication token. Can be created at [https://sentry.io/settings/account/api/auth-tokens/]
- **version** *(required)* – a version identifier for this release.
Can be a version number, a commit hash etc. (Defaults is set to git log -n 1 --format="%h".)
- **version_prefix** *(optional)* - a string prefixed to version.
Releases are global per organization so indipentent projects needs to prefix version number with unique string to avoid conflicts
- **environment** *(optional)* - the environment you’re deploying to. By default framework's environment is used.
For example for symfony, *symfony_env* configuration is read otherwise defaults to 'prod'.
- **ref** *(optional)* – an optional commit reference. This is useful if a tagged version has been provided.
- **refs** *(optional)* - array to indicate the start and end commits for each repository included in a release.
Head commits must include parameters *repository* and *commit*) (the HEAD sha).
They can optionally include *previousCommit* (the sha of the HEAD of the previous release),
which should be specified if this is the first time you’ve sent commit data.
- **commits** *(optional)* - array commits data to be associated with the release.
Commits must include parameters *id* (the sha of the commit), and can optionally include *repository*,
*message*, *author_name*, *author_email* and *timestamp*. By default will send all new commits,
unless it's a first release, then only first 200 will be sent.
- **url** *(optional)* – a URL that points to the release. This can be the path to an online interface to the sourcecode for instance.
- **date_released** *(optional)* – date that indicates when the release went live. If not provided the current time is assumed.
- **sentry_server** *(optional)* – sentry server (if you host it yourself). defaults to hosted sentry service.
- **date_deploy_started** *(optional)* - date that indicates when the deploy started. Defaults to current time.
- **date_deploy_finished** *(optional)* - date that indicates when the deploy ended. If not provided, the current time is used.
- **deploy_name** *(optional)* - name of the deploy
- **git_version_command** *(optional)* - the command that retrieves the git version information (Defaults is set to git log -n 1 --format="%h", other options are git describe --tags --abbrev=0)

```php
// deploy.php

set('sentry', [
    'organization' => 'exampleorg',
    'projects' => [
        'exampleproj'
    ],
    'token' => 'd47828...',
    'version' => '0.0.1',

]);
```

### Suggested Usage

Since you should only notify Sentry of a successful deployment, the deploy:sentry task should be executed right at the end.

```php
// deploy.php

after('deploy', 'deploy:sentry');
```

 */

namespace Deployer;

use Closure;
use DateTime;
use Deployer\Exception\ConfigurationException;
use Deployer\Utility\Httpie;

desc('Notifies Sentry of deployment');
task(
    'deploy:sentry',
    static function () {
        $now = date('c');

        $defaultConfig = [
            'version' => getReleaseGitRef(),
            'version_prefix' => null,
            'refs' => [],
            'ref' => null,
            'commits' => getGitCommitsRefs(),
            'url' => null,
            'date_released' => $now,
            'date_deploy_started' => $now,
            'date_deploy_finished' => $now,
            'sentry_server' => 'https://sentry.io',
            'previous_commit' => null,
            'environment' => get('symfony_env', 'prod'),
            'deploy_name' => null,
        ];

        $config = array_merge($defaultConfig, (array) get('sentry'));
        array_walk(
            $config,
            static function (&$value) use ($config) {
                if (is_callable($value)) {
                    $value = $value($config);
                }
            },
        );

        if (
            !isset($config['organization'], $config['token'], $config['version'])
            || (empty($config['projects']) || !is_array($config['projects']))
        ) {
            throw new \RuntimeException(
                <<<EXAMPLE
                    Required data missing. Please configure sentry:
                    set(
                        'sentry',
                        [
                            'organization' => 'exampleorg',
                            'projects' => [
                                'exampleproj',
                                'exampleproje2'
                            ],
                            'token' => 'd47828...',
                        ]
                    );"
                    EXAMPLE,
            );
        }

        $releaseData = array_filter(
            [
                'version' => ($config['version_prefix'] ?? '') . $config['version'],
                'refs' => $config['refs'],
                'ref' => $config['ref'],
                'url' => $config['url'],
                'commits' => array_slice($config['commits'] ?? [], 0), // reset keys to serialize as array in json
                'dateReleased' => $config['date_released'],
                'projects' => $config['projects'],
                'previousCommit' => $config['previous_commit'],
            ],
        );

        $releasesApiUrl = $config['sentry_server'] . '/api/0/organizations/' . $config['organization'] . '/releases/';
        $response = Httpie::post(
            $releasesApiUrl,
        )
            ->setopt(CURLOPT_TIMEOUT, 10)
            ->header('Authorization', sprintf('Bearer %s', $config['token']))
            ->jsonBody($releaseData)
            ->getJson();

        if (!isset($response['version'], $response['projects'])) {
            throw new \RuntimeException(sprintf('Unable to create a release: %s', print_r($response, true)));
        }

        writeln(
            sprintf(
                '<info>Sentry:</info> Release of version <comment>%s</comment> ' .
                'for projects: <comment>%s</comment> created successfully.',
                $response['version'],
                implode(', ', array_column($response['projects'], 'slug')),
            ),
        );

        $deployData = array_filter(
            [
                'environment' => $config['environment'],
                'name' => $config['deploy_name'],
                'url' => $config['url'],
                'dateStarted' => $config['date_deploy_started'],
                'dateFinished' => $config['date_deploy_finished'],
            ],
        );

        $response = Httpie::post(
            $releasesApiUrl . $response['version'] . '/deploys/',
        )
            ->setopt(CURLOPT_TIMEOUT, 10)
            ->header('Authorization', sprintf('Bearer %s', $config['token']))
            ->jsonBody($deployData)
            ->getJson();

        if (!isset($response['id'], $response['environment'])) {
            throw new \RuntimeException(sprintf('Unable to create a deployment: %s', print_r($response, true)));
        }

        writeln(
            sprintf(
                '<info>Sentry:</info> Deployment <comment>%s</comment> ' .
                'for environment <comment>%s</comment> created successfully',
                $response['id'],
                $response['environment'],
            ),
        );
    },
);

function getPreviousReleaseRevision()
{
    switch (get('update_code_strategy')) {
        case 'local_archive':
        case 'archive':
            if (has('previous_release')) {
                return run('cat {{previous_release}}/REVISION');
            }

            return null;
        case 'clone':
            if (has('previous_release')) {
                cd('{{previous_release}}');
                return trim(run('git rev-parse HEAD'));
            }

            return null;
        default:
            throw new ConfigurationException(parse("Unknown `update_code_strategy` option: {{update_code_strategy}}."));
    }
}

function getCurrentReleaseRevision()
{
    switch (get('update_code_strategy')) {
        case 'local_archive':
        case 'archive':
            return run('cat {{release_path}}/REVISION');

        case 'clone':
            cd('{{release_path}}');
            return trim(run('git rev-parse HEAD'));

        default:
            throw new ConfigurationException(parse("Unknown `update_code_strategy` option: {{update_code_strategy}}."));
    }
}

function getReleaseGitRef(): Closure
{
    return static function ($config = []): string {
        $strategy = get('update_code_strategy');

        if ($strategy === 'archive') {
            cd('{{deploy_path}}/.dep/repo');
        } else {
            cd('{{release_path}}');
        }

        if (isset($config['git_version_command'])) {
            return trim(run($config['git_version_command']));
        }

        if ($strategy !== 'clone') {
            return run('cat {{current_path}}/REVISION');
        }

        return trim(run('git log -n 1 --format="%h"'));
    };
}

function getGitCommitsRefs(): Closure
{
    return static function ($config = []): array {
        $previousReleaseRevision = getPreviousReleaseRevision();
        $currentReleaseRevision = getCurrentReleaseRevision() ?: 'HEAD';

        if ($previousReleaseRevision === null) {
            $commitRange = $currentReleaseRevision;
        } else {
            $commitRange = $previousReleaseRevision . '..' . $currentReleaseRevision;
        }

        try {
            if (get('update_code_strategy') === 'archive') {
                cd('{{deploy_path}}/.dep/repo');
            } else {
                cd('{{release_path}}');
            }

            $result = run(sprintf('git rev-list --pretty="%s" %s', 'format:%H#%an#%ae#%at#%s', $commitRange));
            $lines = array_filter(
                // limit number of commits for first release with many commits
                array_map('trim', array_slice(explode("\n", $result), 0, 200)),
                static function (string $line): bool {
                    return !empty($line) && strpos($line, 'commit') !== 0;
                },
            );

            return array_map(
                static function (string $line): array {
                    [$ref, $authorName, $authorEmail, $timestamp, $message] = explode('#', $line, 5);

                    return [
                        'id' => $ref,
                        'author_name' => $authorName,
                        'author_email' => $authorEmail,
                        'message' => $message,
                        'timestamp' => date(\DateTime::ATOM, (int) $timestamp),
                    ];
                },
                $lines,
            );

        } catch (\Deployer\Exception\RunException $e) {
            writeln($e->getMessage());
            return [];
        }
    };
}


================================================
FILE: contrib/slack.php
================================================
<?php
/*
## Installing

<a href="https://slack.com/oauth/authorize?&client_id=113734341365.225973502034&scope=incoming-webhook"><img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" /></a>


Add hook on deploy:

```php
before('deploy', 'slack:notify');
```

## Configuration

- `slack_webhook` – slack incoming webhook url, **required**
  ```
  set('slack_webhook', 'https://hooks.slack.com/...');
  ```
- `slack_channel` - channel to send notification to. The default is the channel configured in the webhook
- `slack_title` – the title of application, default `{{application}}`
- `slack_text` – notification message template, markdown supported
  ```
  set('slack_text', '_{{user}}_ deploying `{{what}}` to *{{where}}*');
  ```
- `slack_success_text` – success template, default:
  ```
  set('slack_success_text', 'Deploy to *{{where}}* successful');
  ```
- `slack_failure_text` – failure template, default:
  ```
  set('slack_failure_text', 'Deploy to *{{where}}* failed');
  ```

- `slack_color` – color's attachment
- `slack_success_color` – success color's attachment
- `slack_failure_color` – failure color's attachment
- `slack_fields` - set attachments fields for pretty output in Slack, default:
  ```
  set('slack_fields', []);
  ```

## Usage

If you want to notify only about beginning of deployment add this line only:

```php
before('deploy', 'slack:notify');
```

If you want to notify about successful end of deployment add this too:

```php
after('deploy:success', 'slack:notify:success');
```

If you want to notify about failed deployment add this too:

```php
after('deploy:failed', 'slack:notify:failure');
```

 */

namespace Deployer;

use Deployer\Utility\Httpie;

// Channel to publish to, when false the default channel the webhook will be used
set('slack_channel', false);

// Title of project
set('slack_title', function () {
    return get('application', 'Project');
});

// Deploy message
set('slack_text', '_{{user}}_ deploying `{{what}}` to *{{where}}*');
set('slack_success_text', 'Deploy to *{{where}}* successful');
set('slack_failure_text', 'Deploy to *{{where}}* failed');
set('slack_rollback_text', '_{{user}}_ rolled back changes on *{{where}}*');
set('slack_fields', []);

// Color of attachment
set('slack_color', '#4d91f7');
set('slack_success_color', '#00c100');
set('slack_failure_color', '#ff0909');
set('slack_rollback_color', '#eba211');

function checkSlackAnswer($result)
{
    if ('invalid_token' === $result) {
        warning('Invalid Slack token');
        return false;
    }
    return true;
}

desc('Notifies Slack');
task('slack:notify', function () {
    if (!get('slack_webhook', false)) {
        warning('No Slack webhook configured');
        return;
    }

    $attachment = [
        'title' => get('slack_title'),
        'text' => get('slack_text'),
        'color' => get('slack_color'),
        'mrkdwn_in' => ['text'],
    ];

    $result = Httpie::post(get('slack_webhook'))->jsonBody(['channel' => get('slack_channel'), 'attachments' => [$attachment]])->send();
    checkSlackAnswer($result);
})
    ->once()
    ->hidden();

desc('Notifies Slack about deploy finish');
task('slack:notify:success', function () {
    if (!get('slack_webhook', false)) {
        warning('No Slack webhook configured');
        return;
    }

    $attachment = [
        'title' => get('slack_title'),
        'text' => get('slack_success_text'),
        'color' => get('slack_success_color'),
        'fields' => get('slack_fields'),
        'mrkdwn_in' => ['text'],
    ];

    $result = Httpie::post(get('slack_webhook'))->jsonBody(['channel' => get('slack_channel'), 'attachments' => [$attachment]])->send();
    checkSlackAnswer($result);
})
    ->once()
    ->hidden();

desc('Notifies Slack about deploy failure');
task('slack:notify:failure', function () {
    if (!get('slack_webhook', false)) {
        warning('No Slack webhook configured');
        return;
    }

    $attachment = [
        'title' => get('slack_title'),
        'text' => get('slack_failure_text'),
        'color' => get('slack_failure_color'),
        'mrkdwn_in' => ['text'],
    ];

    $result = Httpie::post(get('slack_webhook'))->jsonBody(['channel' => get('slack_channel'), 'attachments' => [$attachment]])->send();
    checkSlackAnswer($result);
})
    ->once()
    ->hidden();

desc('Notifies Slack about rollback');
task('slack:notify:rollback', function () {
    if (!get('slack_webhook', false)) {
        warning('No Slack webhook configured');
        return;
    }

    $attachment = [
        'title' => get('slack_title'),
        'text' => get('slack_rollback_text'),
        'color' => get('slack_rollback_color'),
        'mrkdwn_in' => ['text'],
    ];

    $result = Httpie::post(get('slack_webhook'))->jsonBody(['channel' => get('slack_channel'), 'attachments' => [$attachment]])->send();
    checkSlackAnswer($result);
})
    ->once()
    ->hidden();


================================================
FILE: contrib/supervisord-monitor.php
================================================
<?php
/*
### Description
This is a recipe that uses the [Supervisord server monitoring project](https://github.com/mlazarov/supervisord-monitor).

With this recipe the possibility is created to restart a supervisord process through the Supervisor Monitor webtool, by using cURL. This workaround is particular usefull when the deployment user has unsuficient rights to restart a daemon process from the cli.

### Configuration

```
set('supervisord', [
    'uri' => 'https://youruri.xyz/supervisor',
    'basic_auth_user' => 'username',
    'basic_auth_password' => 'password',
    'process_name' => 'process01',
]);
```

or

```
set('supervisord_uri', 'https://youruri.xyz/supervisor');
set('supervisord_basic_auth_user', 'username');
set('supervisord_basic_auth_password', 'password');
set('supervisord_process_name', 'process01');
```

- `supervisord` – array with configuration for Supervisord
    - `uri` – URI to the Supervisord monitor page
    - `basic_auth_user` – Basic auth username to access the URI
    - `basic_auth_password` – Basic auth password to access the URI
    - `process_name` – the process name, as visible in the Supervisord monitor page. Multiple processes can be listed here, comma separated

### Task

- `supervisord-monitor:restart` Restarts given processes
- `supervisord-monitor:stop` Stops given processes
- `supervisord-monitor:start` Starts given processes

### Usage

A complete example with configs, staging and deployment

```
<?php

namespace Deployer;
use Dotenv\Dotenv;

require 'vendor/autoload.php';

require 'supervisord_monitor.php';

// Project name
set('application', 'myproject.com');

// Project repository
set('repository', 'git@github.com:myorg/myproject.com');

set('supervisord', [
    'uri' => 'https://youruri.xyz/supervisor',
    'basic_auth_user' => 'username',
    'basic_auth_password' => 'password',
    'process_name' => 'process01',
]);

host('staging.myproject.com')
    ->set('branch', 'develop')
    ->set('labels', ['stage' => 'staging']);

host('myproject.com')
    ->set('branch', 'main')
    ->set('labels', ['stage' => 'production']);

// Tasks
task('build', function () {
    run('cd {{release_path}} && build');
});

task('deploy', [
    'build',
    'supervisord',
]);

task('supervisord', ['supervisord-monitor:restart'])
    ->select('stage=production');
```
*/

namespace Deployer;

use Deployer\Utility\Httpie;

function supervisordCheckConfig()
{
    $config = get('supervisord', []);
    foreach ($config as $key => $value) {
        if ($value) {
            set('supervisord_' . $key, $value);
        }
    }

    if (!get('supervisord_uri') ||
        !get('supervisord_basic_auth_user') ||
        !get('supervisord_basic_auth_password') ||
        !get('supervisord_process_name')) {
        throw new \RuntimeException("<comment>Please configure Supervisord config:</comment> <info>set('supervisord', array('uri' => 'yourdomain.xyz/supervisor', 'basic_auth_user' => 'abc' , 'basic_auth_password' => 'xyz', 'process_name' => 'process01,process02'));</info> or <info>set('supervisord_uri', 'yourdomain.xyz/supervisor'); set('supervisord_basic_auth_user', 'abc'); etc</info>");
    }
}

function supervisordGetBasicAuthToken()
{
    return 'Basic ' . base64_encode(get('supervisord_basic_auth_user') . ':' . get('supervisord_basic_auth_password'));
}

function supervisordIsAuthenticated()
{
    supervisordCheckConfig();

    $authResponseInfo = [];
    Httpie::post(get('supervisord_uri'))->header('Authorization', supervisordGetBasicAuthToken())->send($authResponseInfo);

    return $authResponseInfo['http_code'] === 200;
}

function supervisordControlAction($name, $action = 'stop')
{
    $stopResponseInfo = [];
    Httpie::post(get('supervisord_uri') . '/control/' . $action . '/localhost/' . $name)->header('Authorization', supervisordGetBasicAuthToken())->send($stopResponseInfo);

    return $stopResponseInfo['http_code'] === 200;
}

task('supervisord-monitor:restart', function () {
    if (supervisordIsAuthenticated()) {
        $names = explode(',', get('supervisord_process_name'));
        foreach ($names as $name) {
            $name = trim($name);
            if (supervisordControlAction($name, 'stop')) {
                writeln('Daemon [' . $name . '] stopped');
                if (supervisordControlAction($name, 'start')) {
                    writeln('Daemon [' . $name . '] started');
                }
            }
        }
    } else {
        writeln('Authentication failed');
    }
});

task('supervisord-monitor:stop', function () {
    if (supervisordIsAuthenticated()) {
        $names = explode(',', get('supervisord_process_name'));
        foreach ($names as $name) {
            $name = trim($name);
            if (supervisordControlAction($name, 'stop')) {
                writeln('Daemon [' . $name . '] stopped');
            }
        }
    } else {
        writeln('Authentication failed');
    }
});

task('supervisord-monitor:start', function () {
    if (supervisordIsAuthenticated()) {
        $names = explode(',', get('supervisord_process_name'));
        foreach ($names as $name) {
            $name = trim($name);
            if (supervisordControlAction($name, 'start')) {
                writeln('Daemon [' . $name . '] started');
            }
        }
    } else {
        writeln('Authentication failed');
    }
});


================================================
FILE: contrib/telegram.php
================================================
<?php
/*
## Installing
  1. Create telegram bot with [BotFather](https://t.me/BotFather) and grab the token provided
  2. Send `/start` to your bot and open https://api.telegram.org/bot{$TELEGRAM_TOKEN_HERE}/getUpdates
  3. Take chat_id from response


Add hook on deploy:

```php
before('deploy', 'telegram:notify');
```

## Configuration

- `telegram_token` – telegram bot token, **required**
- `telegram_chat_id` — chat ID to push messages to
- `telegram_proxy` - proxy connection string in [CURLOPT_PROXY](https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html) form like:
  ```
  http://proxy:80
  socks5://user:password@host:3128
   ```
- `telegram_title` – the title of application, default `{{application}}`
- `telegram_text` – notification message template
  ```
  _{{user}}_ deploying `{{what}}` to *{{where}}*
  ```
- `telegram_success_text` – success template, default:
  ```
  Deploy to *{{where}}* successful

  ```
- `telegram_failure_text` – failure template, default:
  ```
  Deploy to *{{where}}* failed
  ```

## Usage

If you want to notify only about beginning of deployment add this line only:

```php
before('deploy', 'telegram:notify');
```

If you want to notify about successful end of deployment add this too:

```php
after('deploy:success', 'telegram:notify:success');
```
If you want to notify about failed deployment add this too:

```php
after('deploy:failed', 'telegram:notify:failure');


 */

namespace Deployer;

use Deployer\Utility\Httpie;

// Title of project
set('telegram_title', function () {
    return get('application', 'Project');
});

// Telegram settings
set('telegram_token', function () {
    throw new \Exception('Please, configure "telegram_token" parameter.');
});
set('telegram_chat_id', function () {
    throw new \Exception('Please, configure "telegram_chat_id" parameter.');
});
set('telegram_url', function () {
    return 'https://api.telegram.org/bot' . get('telegram_token') . '/sendmessage';
});

// Deploy message
set('telegram_text', '_{{user}}_ deploying `{{what}}` to *{{where}}*');
set('telegram_success_text', 'Deploy to *{{where}}* successful');
set('telegram_failure_text', 'Deploy to *{{where}}* failed');


desc('Notifies Telegram');
task('telegram:notify', function () {
    if (!get('telegram_token', false)) {
        warning('No Telegram token configured');
        return;
    }

    if (!get('telegram_chat_id', false)) {
        warning('No Telegram chat id configured');
        return;
    }

    $telegramUrl = get('telegram_url') . '?' . http_build_query(
        [
            'chat_id' => get('telegram_chat_id'),
            'text' => get('telegram_text'),
            'parse_mode' => 'Markdown',
        ],
    );

    $httpie = Httpie::get($telegramUrl);

    if (get('telegram_proxy', '') !== '') {
        $httpie = $httpie->setopt(CURLOPT_PROXY, get('telegram_proxy'));
    }

    $httpie->send();
})
    ->once()
    ->hidden();

desc('Notifies Telegram about deploy finish');
task('telegram:notify:success', function () {
    if (!get('telegram_token', false)) {
        warning('No Telegram token configured');
        return;
    }

    if (!get('telegram_chat_id', false)) {
        warning('No Telegram chat id configured');
        return;
    }

    $telegramUrl = get('telegram_url') . '?' . http_build_query(
        [
            'chat_id' => get('telegram_chat_id'),
            'text' => get('telegram_success_text'),
            'parse_mode' => 'Markdown',
        ],
    );

    $httpie = Httpie::get($telegramUrl);

    if (get('telegram_proxy', '') !== '') {
        $httpie = $httpie->setopt(CURLOPT_PROXY, get('telegram_proxy'));
    }

    $httpie->send();
})
  ->once()
  ->hidden();

desc('Notifies Telegram about deploy failure');
task('telegram:notify:failure', function () {
    if (!get('telegram_token', false)) {
        warning('No Telegram token configured');
        return;
    }

    if (!get('telegram_chat_id', false)) {
        warning('No Telegram chat id configured');
        return;
    }

    $telegramUrl = get('telegram_url') . '?' . http_build_query(
        [
            'chat_id' => get('telegram_chat_id'),
            'text' => get('telegram_failure_text'),
            'parse_mode' => 'Markdown',
        ],
    );

    $httpie = Httpie::get($telegramUrl);

    if (get('telegram_proxy', '') !== '') {
        $httpie = $httpie->setopt(CURLOPT_PROXY, get('telegram_proxy'));
    }

    $httpie->send();
})
  ->once()
  ->hidden();


================================================
FILE: contrib/webpack_encore.php
================================================
<?php
/*

## Configuration

- **webpack_encore/package_manager** *(optional)*: set yarn or npm. We try to find if yarn or npm is available and used.

## Usage

```php
// For Yarn
after('deploy:update_code', 'yarn:install');
// For npm
after('deploy:update_code', 'npm:install');

after('deploy:update_code', 'webpack_encore:build');
```
 */

namespace Deployer;

require_once __DIR__ . '/npm.php';
require_once __DIR__ . '/yarn.php';

set('webpack_encore/package_manager', function () {
    if (test('[ -f {{release_path}}/yarn.lock ]')) {
        return 'yarn';
    }

    return 'npm';
});

set('webpack_encore/env', 'production');

desc('Runs webpack encore build');
task('webpack_encore:build', function () {
    $packageManager = get('webpack_encore/package_manager');

    if (!in_array($packageManager, ['npm', 'yarn'], true)) {
        throw new \Exception(sprintf('Package Manager "%s" is not supported', $packageManager));
    }

    run("cd {{release_path}} && {{bin/$packageManager}} run encore {{webpack_encore/env}}");
});


================================================
FILE: contrib/workplace.php
================================================
<?php
/*
This recipes works with Custom Integrations and Publishing Bots.


Add hook on deploy:

```
before('deploy', 'workplace:notify');
```

## Configuration

 - `workplace_webhook` - incoming workplace webhook **required**
   ```
   // With custom integration
   set('workplace_webhook', 'https://graph.facebook.com/<GROUP_ID>/feed?access_token=<ACCESS_TOKEN>');

   // With publishing bot
   set('workplace_webhook', 'https://graph.facebook.com/v3.0/group/feed?access_token=<ACCESS_TOKEN>');

   // Use markdown on message
   set('workplace_webhook', 'https://graph.facebook.com/<GROUP_ID>/feed?access_token=<ACCESS_TOKEN>&formatting=MARKDOWN');
   ```

 - `workplace_text` - notification message
   ```
   set('workplace_text', '_{{user}}_ deploying `{{what}}` to *{{where}}*');
   ```

 - `workplace_success_text` – success template, default:
  ```
  set('workplace_success_text', 'Deploy to *{{where}}* successful');
  ```
 - `workplace_failure_text` – failure template, default:
  ```
  set('workplace_failure_text', 'Deploy to *{{where}}* failed');
  ```
 - `workplace_edit_post` – whether to create a new post for deploy result, or edit the first one created, default creates a new post:
  ```
  set('workplace_edit_post', false);
  ```

## Usage

If you want to notify only about beginning of deployment add this line only:

```php
before('deploy', 'workplace:notify');
```

If you want to notify about successful end of deployment add this too:

```php
after('deploy:success', 'workplace:notify:success');
```

If you want to notify about failed deployment add this too:

```php
after('deploy:failed', 'workplace:notify:failure');
```

 */

namespace Deployer;

use Deployer\Utility\Httpie;

// Deploy message
set('workplace_text', '_{{user}}_ deploying `{{what}}` to *{{where}}*');
set('workplace_success_text', 'Deploy to *{{where}}* successful');
set('workplace_failure_text', 'Deploy to *{{where}}* failed');

// By default, create a new post for every message
set('workplace_edit_post', false);

desc('Notifies Workplace');
task('workplace:notify', function () {
    if (!get('workplace_webhook', false)) {
        return;
    }
    $url = get('workplace_webhook') . '&message=' . urlencode(get('workplace_text'));
    $response = Httpie::post($url)->getJson();

    if (get('workplace_edit_post', false)) {
        // Endpoint will be something like: https//graph.facebook.com/<POST_ID>?<QUERY_PARAMS>
        $url = sprintf(
            '%s://%s/%s?%s',
            parse_url(get('workplace_webhook'), PHP_URL_SCHEME),
            parse_url(get('workplace_webhook'), PHP_URL_HOST),
            $response['id'],
            parse_url(get('workplace_webhook'), PHP_URL_QUERY),
        );
        // Replace the webhook with a url that points to the created post
        set('workplace_webhook', $url);
    }
})
    ->once()
    ->hidden();

desc('Notifies Workplace about deploy finish');
task('workplace:notify:success', function () {
    if (!get('workplace_webhook', false)) {
        return;
    }
    $url = get('workplace_webhook') . '&message=' . urlencode(get('workplace_success_text'));
    Httpie::post($url)->send();
})
    ->once()
    ->hidden();

desc('Notifies Workplace about deploy failure');
task('workplace:notify:failure', function () {
    if (!get('workplace_webhook', false)) {
        return;
    }
    $url = get('workplace_webhook') . '&message=' . urlencode(get('workplace_failure_text'));
    Httpie::post($url)->send();
})
    ->once()
    ->hidden();


================================================
FILE: contrib/yammer.php
================================================
<?php
/*

Add hook on deploy:

```php
before('deploy', 'yammer:notify');
```

## Configuration

- `yammer_url` – The URL to the message endpoint, default is https://www.yammer.com/api/v1/messages.json
- `yammer_token` *(required)* – Yammer auth token
- `yammer_group_id` *(required)* - Group ID
- `yammer_title` – the title of application, default `{{application}}`
- `yammer_body` – notification message template, default:
  ```
  <em>{{user}}</em> deploying {{what}} to <strong>{{where}}</strong>
  ```
- `yammer_success_body` – success template, default:
  ```
  Deploy to <strong>{{where}}</strong> successful
  ```
- `yammer_failure_body` – failure template, default:
  ```
  Deploy to <strong>{{where}}</strong> failed
  ```

## Usage

If you want to notify only about beginning of deployment add this line only:

```php
before('deploy', 'yammer:notify');
```

If you want to notify about successful end of deployment add this too:

```php
after('deploy:success', 'yammer:notify:success');
```

If you want to notify about failed deployment add this too:

```php
after('deploy:failed', 'yammer:notify:failure');
```

 */

namespace Deployer;

use Deployer\Utility\Httpie;

set('yammer_url', 'https://www.yammer.com/api/v1/messages.json');

// Title of project
set('yammer_title', function () {
    return get('application', 'Project');
});

// Deploy message
set('yammer_body', '<em>{{user}}</em> deploying {{what}} to <strong>{{where}}</strong>');
set('yammer_success_body', 'Deploy to <strong>{{where}}</strong> successful');
set('yammer_failure_body', 'Deploy to <strong>{{where}}</strong> failed');

desc('Notifies Yammer');
task('yammer:notify', function () {
    $params = [
        'is_rich_text' => 'true',
        'message_type' => 'announcement',
        'group_id' => get('yammer_group_id'),
        'title' => get('yammer_title'),
        'body' => get('yammer_body'),
    ];

    Httpie::post(get('yammer_url'))
        ->header('Authorization', 'Bearer ' . get('yammer_token'))
        ->header('Content-type', 'application/json')
        ->jsonBody($params)
        ->send();
})
    ->once()
    ->hidden();

desc('Notifies Yammer about deploy finish');
task('yammer:notify:success', function () {
    $params = [
        'is_rich_text' => 'true',
        'message_type' => 'announcement',
        'group_id' => get('yammer_group_id'),
        'title' => get('yammer_title'),
        'body' => get('yammer_success_body'),
    ];

    Httpie::post(get('yammer_url'))
        ->header('Authorization', 'Bearer ' . get('yammer_token'))
        ->header('Content-type', 'application/json')
        ->jsonBody($params)
        ->send();
})
    ->once()
    ->hidden();

desc('Notifies Yammer about deploy failure');
task('yammer:notify:failure', function () {
    $params = [
        'is_rich_text' => 'true',
        'message_type' => 'announcement',
        'group_id' => get('yammer_group_id'),
        'title' => get('yammer_title'),
        'body' => get('yammer_failure_body'),
    ];

    Httpie::post(get('yammer_url'))
        ->header('Authorization', 'Bearer ' . get('yammer_token'))
        ->header('Content-type', 'application/json')
        ->jsonBody($params)
        ->send();
})
    ->once()
    ->hidden();


================================================
FILE: contrib/yarn.php
================================================
<?php
/*
## Configuration

- **bin/yarn** *(optional)*: set Yarn binary, automatically detected otherwise.

## Usage

```php
after('deploy:update_code', 'yarn:install');
```
 */

namespace Deployer;

set('bin/yarn', function () {
    return which('yarn');
});

// In there is a {{previous_release}}, node_modules will be copied from it before installing deps with yarn.
desc('Installs Yarn packages');
task('yarn:install', function () {
    if (has('previous_release')) {
        if (test('[ -d {{previous_release}}/node_modules ]')) {
            run('cp -R {{previous_release}}/node_modules {{release_path}}');
        }
    }
    run("cd {{release_path}} && {{bin/yarn}}");
});


================================================
FILE: docs/KNOWN_BUGS.md
================================================
# Known Bugs

## Ubuntu 14.04, Coreutils 8.21

There are known bugs with relative symlinks `ln --relative`, which may cause the rollback command to fail.

Add the following line to your _deploy.php_ file:

```php
set('use_relative_symlink', false);
```

## OpenSSH_7.2p2

ControlPersist causes stderr to be left open until the master connection times out.

- https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=714526
- https://bugzilla.mindrot.org/show_bug.cgi?id=1988

## cURL 7.29.0

Certificate verification fails with multiple https urls.

- https://bugzilla.redhat.com/show_bug.cgi?id=1241172

## Rsync (3.1.3)

Artifact upload with `rsync` is interrupted after the first chunk of data upload.

```
The command "rsync -azP -e 'ssh -A -p *** -o UserKnownHostsFile=/dev/null
  -o StrictHostKeyChecking=no' 'artifacts/artifact.tar.gz' 'deploy@ssh.XXX.io:/srv/releases/2009076181'" failed.

Exit Code: 255(Unknown error)

Output:
================
sending incremental file list
artifact.tar.gz
     32,768   0%    0.00kB/s    0:00:00

Error Output:
================
client_loop: send disconnect: Broken pipe

rsync: [sender] write error: Broken pipe (32)
```

In order to resolve (workaround) the issue, you need to add `--bwlimit=4096` to the list of options.

Example:

```php
task('artifact:upload', function () {
    upload(get('artifact_path'), '{{release_path}}', ['options' => ['--bwlimit=4096']]);
});
```

The issue was also described in the [Github Action](https://github.com/deployphp/action/issues/35).


================================================
FILE: docs/UPGRADE.md
================================================
# Upgrade a major version

## Upgrade from 7.x to 8.x

- `run()` and `runLocally()` doesn't accept `options` parameter anymore. Use named arguments instead.
   - `no_throw` is now `nothrow`.
   - `real_time_output` is now `forceOutput`.
   - `idle_timeout` is now `idleTimeout`.

## Upgrade from 6.x to 7.x

### Step 1: Update deploy.php

1. Change config `hostname` to `alias`.
2. Change config `real_hostname` to `hostname`.
3. Change config `user` to `remote_user`.
4. Update `host()` definitions:
   1. Add `set` prefix to all setters: `identityFile` -> `setIdentityFile` or `set('identity_file')`
   2. Update `host(...)->addSshOption('UserKnownHostsFile', '/dev/null')` to `host(...)->setSshArguments(['-o UserKnownHostsFile=/dev/null']);`
   3. Replace _stage_ with labels, i.e.
      ```php
      host('deployer.org')
          ->set('labels', ['stage' => 'prod']);
      ```
      When deploying instead of `dep deploy prod` use `dep deploy stage=prod`.
   4. `alias()` is deleted, `host()` itself sets alias and hostname, to override hostname use `setHostname()`.
5. Update `task()` definitions.
   1. Replace `onRoles()` with `select()`:
      ```php
      task(...)
          ->select('stage=prod');
      ```
   2. Don't use string-based task definition, it's not available anymore. Don't forget to set correct working directory.
      ```php
      # from
      task('deploy:npm-install', 'npm clean-install');
      
      # to
      task('deploy:npm-install', function() {
          cd('{{release_path}}');
          run('npm clean-install');
      });
      ```
   3. Remove `shallow()` tasks options.      
6. Third party recipes now live inside main Deployer repo in _contrib_:
   ```php
   require 'contrib/rsync.php';
   ```
7. Replace `inventory()` with `import()`. It now can import hosts, configs, tasks:

   ```yaml
   import: recipe/common.php

   config:
     application: deployer
     shared_dirs:
       - uploads
       - storage/logs/
       - storage/db
     shared_files:
       - .env
       - config/test.yaml
     keep_releases: 3
     http_user: false

   hosts:
     prod:
       local: true

   tasks:
     deploy:
       - deploy:prepare
       - deploy:vendors
       - deploy:publish

     deploy:vendors:
       - run: "cd {{release_path}} && echo {{bin/composer}} {{composer_options}} 2>&1"
   ```

8. Rename task `success` to `deploy:success` and `cleanup` to `deploy:cleanup`.
9. Verbosity functions (`isDebug()`, etc) got deleted. Use `output()->isDebug()` instead.
10. `runLocally()` commands are executed relative to the recipe file directory. This behaviour can be overridden via an environment variable:
    ```
    DEPLOYER_ROOT=. vendor/bin/dep taskname
    ```
11. Replace `local()` tasks with combination of `once()` and `runLocally()` func.
12. Replace `locateBinaryPath()` with `which()` func.
13. Replace `default_stage` with `default_selector`, and adjust the value accordingly (for example: "prod" to "stage=prod").
14. Replace `onHosts()` and `onStage()` with [labels & selectors](selector.md).
15. Replace `setPrivate()` with [`hidden()`](tasks.md#hidden).
16. Configuration property `writable_recursive` defaults to `false`. This behaviour can be overridden with:
   ```php
   set('writable_recursive', true);
   ```
17. `.git` directory is not present in release directory anymore. The previous behavior can be restored with:
   ```php
   set('update_code_strategy', 'clone');
   ```

### Step 2: Deploy

Since the release history numbering is not compatible between v6 and v7, you need to specify the `release_name` manually for the first time. Otherwise you start with release 1.

1. Find out next release name (ssh to the host, `ls` releases dir, find the biggest number). Example: `42`.
2. Deploy with release_name:
   ```
   dep deploy -o release_name=43
   ```

:::note
In case a rollback is needed, manually change the `current` symlink:

```
ln -nfs releases/42 current
```

:::

:::note
In case there are multiple hosts with different release names, you should create a `{{deploy_path}}/.dep/latest_release` file in each host with the current release number of that particular host.
:::

## Upgrade from 5.x to 6.x

1. Changed branch option priority

   If you have host definition with `branch(...)` parameter, adding `--branch` option will not override it any more.
   If no `branch(...)` parameter persists, branch will be fetched from current local git branch.

   ```php
   host('prod')
       ->set('branch', 'production')
   ```

   In order to return to old behavior add checking of `--branch` option.

   ```php
   host('prod')
       ->set('branch', function () {
           return input()->getOption('branch') ?: 'production';
       })
   ```

2. Add `deploy:info` task to the beginning to `deploy` task.
3. `run` returns string instead of `Deployer\Type\Result`

   Now `run` and `runLocally` returns `string` instead of `Deployer\Type\Result`.
   Replace method calls as:

   - `run('command')->toString()` → `run('command')`
   - `run('if command; then echo "true"; fi;')->toBool()` → `test('command')`

4. `env_vars` renamed to `env`

   - `set('env_vars', 'FOO=bar');` → `set('env', ['FOO' => 'bar']);`

   If your are using Symfony recipe, then you need to change `env` setting:

   - `set('env', 'prod');` → `set('symfony_env', 'prod');`

## Upgrade from 4.x to 5.x

1. Servers to Hosts

   - `server($hostname)` to `host($hostname)`, and `server($name, $hostname)` to `host($name)->hostname($hostname)`
   - `localServer($name)` to `localhost()`
   - `cluster($name, $nodes, $port)` to `hosts(...$hodes)`
   - `serverList($file)` to `inventory($file)`

   If you need to deploy to same server use [host aliases](https://deployer.org/docs/hosts#host-aliases):

   ```php
   host('domain.com/green', 'domain.com/blue')
       ->set('deploy_path', '~/{{hostname}}')
       ...
   ```

   Or you can define different hosts with same hostname:

   ```php
   host('production')
       ->hostname('domain.com')
       ->set('deploy_path', '~/production')
       ...

   host('beta')
       ->hostname('domain.com')
       ->set('deploy_path', '~/beta')
       ...
   ```

2. Configuration options

   - Rename `{{server.name}}` to `{{hostname}}`

3. DotArray syntax

   In v5 access to nested arrays in config via dot notation was removed.
   If you was using it, consider to move to plain config options.

   Refactor this:

   ```php
   set('a', ['b' => 1]);

   // ...

   get('a.b');
   ```

   To:

   ```php
   set('a_b', 1);

   // ...

   get('a_b');
   ```

4. Credentials

   Best practice in new v5 is to omit credentials for connection in `deploy.php` and write them in `~/.ssh/config` instead.

   - `identityFile($publicKeyFile,, $privateKeyFile, $passPhrase)` to `identityFile($privateKeyFile)`
   - `pemFile($pemFile)` to `identityFile($pemFile)`
   - `forwardAgent()` to `forwardAgent(true)`

5. Tasks constraints

   - `onlyOn` to `onHosts`
   - `onlyOnStage` to `onStage`

## Upgrade from 3.x to 4.x

1. Namespace for functions

   Add to beginning of _deploy.php_ next line:

   ```php
   use function Deployer\{server, task, run, set, get, add, before, after};
   ```

   If you are using PHP version less than 5.6, you can use this:

   ```php
   namespace Deployer;
   ```

2. `env()` to `set()`/`get()`

   Rename all calls `env($name, $value)` to `set($name, $value)`.

   Rename all rvalue `env($name)` to `get($name)`.

   Rename all `server(...)->env(...)` to `server(...)->set(...)`.

3. Moved _NonFatalException_

   Rename `Deployer\Task\NonFatalException` to `Deployer\Exception\NonFatalException`.

4. Prior release cleanup

   Due to changes in release management, the new cleanup task will ignore any prior releases deployed with 3.x. These will need to be manually removed after migrating to and successfully releasing via 4.x.

## Upgrade from 2.x to 3.x

1. ### `->path('...')`

   Replace your server paths configuration:

   ```php
   server(...)
     ->path(...);
   ```

   to:

   ```php
   server(...)
     ->env('deploy_path', '...');
   ```


================================================
FILE: docs/api.md
================================================
<!-- DO NOT EDIT THIS FILE! -->
<!-- Instead edit src/functions.php -->
<!-- Then run bin/docgen -->

# API Reference

## host()

```php
host(string ...$hostname): Host|ObjectProxy
```

Defines a host or hosts.
```php
host('example.org');
host('prod.example.org', 'staging.example.org');
```

Inside task can be used to get `Host` instance of an alias.
```php
task('test', function () {
    $port = host('example.org')->get('port');
});
```


## localhost()

```php
localhost(string ...$hostnames): Localhost|ObjectProxy
```

Define a local host.
Deployer will not connect to this host, but will execute commands locally instead.

```php
localhost('ci'); // Alias and hostname will be "ci".
```


## currentHost()

```php
currentHost(): Host
```

Returns current host.


## select()

```php
select(string $selector): array
```

Returns hosts based on provided selector.

```php
on(select('stage=prod, role=db'), function (Host $host) {
    ...
});
```



## selectedHosts()

```php
selectedHosts(): array
```

Returns array of hosts selected by user via CLI.



## import()

```php
import(string $file): void
```

Import other php or yaml recipes.

```php
import('recipe/common.php');
```

```php
import(__DIR__ . '/config/hosts.yaml');
```


## desc()

```php
desc(?string $title = null): ?string
```

Set task description.


## task()

```php
task(string $name, callable|array|null $body = null): Task
```

Define a new task and save to tasks list.

Alternatively get a defined task.



| Argument | Type | Comment |
|---|---|---|
| `$name` | `string` | Name of current task. |
| `$body` | `callable` or `array` or `null` | Callable task, array of other tasks names or nothing to get a defined tasks |

## before()

```php
before(string $task, string|callable $do): ?Task
```

Call that task before specified task runs.




| Argument | Type | Comment |
|---|---|---|
| `$task` | `string` | The task before $that should be run. |
| `$do` | `string` or `callable` | The task to be run. |

## after()

```php
after(string $task, string|callable $do): ?Task
```

Call that task after specified task runs.




| Argument | Type | Comment |
|---|---|---|
| `$task` | `string` | The task after $that should be run. |
| `$do` | `string` or `callable` | The task to be run. |

## fail()

```php
fail(string $task, string|callable $do): ?Task
```

Setup which task run on failure of $task.
When called multiple times for a task, previous fail() definitions will be overridden.




| Argument | Type | Comment |
|---|---|---|
| `$task` | `string` | The task which need to fail so $that should be run. |
| `$do` | `string` or `callable` | The task to be run. |

## option()

```php
option(string $name, $shortcut = null, ?int $mode = null, string $description = '', $default = null): void
```

Add users options.



| Argument | Type | Comment |
|---|---|---|
| `$name` | `string` | The option name |
| `$shortcut` | `string` or `array` or `null` | The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts |
| `$mode` | `int` or `null` | The option mode: One of the VALUE_* constants |
| `$description` | `string` | A description text |
| `$default` | `string` or `string[]` or `int` or `bool` or `null` | The default value (must be null for self::VALUE_NONE) |

## cd()

```php
cd(string $path): void
```

Change the current working directory.

```php
cd('~/myapp');
run('ls'); // Will run `ls` in ~/myapp.
```


## become()

```php
become(string $user): \Closure
```

Change the current user.

Usage:
```php
$restore = become('deployer');

// do something

$restore(); // revert back to the previous user
```



## within()

```php
within(string $path, callable $callback): mixed
```

Execute a callback within a specific directory and revert back to the initial working directory.



## run()

```php
run(
    string  $command,
    ?string $cwd = null,
    ?array  $env = null,
    #[\SensitiveParameter]
    ?string $secret = null,
    ?bool   $nothrow = false,
    ?bool   $forceOutput = false,
    ?int    $timeout = null,
    ?int    $idleTimeout = null,
): string 
```

Executes given command on remote host.

Examples:

```php
run('echo hello world');
run('cd {{deploy_path}} && git status');
run('password %secret%', secret: getenv('CI_SECRET'));
run('curl medv.io', timeout: 5);
```

```php
$path = run('readlink {{deploy_path}}/current');
run("echo $path");
```



| Argument | Type | Comment |
|---|---|---|
| `$command` | `string` | Command to run on remote host. |
| `$cwd` | `string` or `null` | Sets the process working directory. If not set {{working_path}} will be used. |
| `$timeout` | `int` or `null` | Sets the process timeout (max. runtime). The timeout in seconds (default: 300 sec; see {{default_timeout}}, `null` to disable). |
| `$idleTimeout` | `int` or `null` | Sets the process idle timeout (max. time since last output) in seconds. |
| `$secret` | `string` or `null` | Placeholder `%secret%` can be used in command. Placeholder will be replaced with this value and will not appear in any logs. |
| `$env` | `array` or `null` | Array of environment variables: `run('echo $KEY', env: ['key' => 'value']);` |
| `$forceOutput` | `bool` or `null` | Print command output in real-time. |
| `$nothrow` | `bool` or `null` | Don't throw an exception of non-zero exit code. |

## runLocally()

```php
runLocally(
    string  $command,
    ?string $cwd = null,
    ?int    $timeout = null,
    ?int    $idleTimeout = null,
    #[\SensitiveParameter]
    ?string $secret = null,
    ?array  $env = null,
    ?bool   $forceOutput = false,
    ?bool   $nothrow = false,
    ?string $shell = null,
): string 
```

Execute commands on a local machine.

Examples:

```php
$user = runLocally('git config user.name');
runLocally("echo $user");
```




| Argument | Type | Comment |
|---|---|---|
| `$command` | `string` | Command to run on localhost. |
| `$cwd` | `string` or `null` | Sets the process working directory. If not set {{working_path}} will be used. |
| `$timeout` | `int` or `null` | Sets the process timeout (max. runtime). The timeout in seconds (default: 300 sec, `null` to disable). |
| `$idleTimeout` | `int` or `null` | Sets the process idle timeout (max. time since last output) in seconds. |
| `$secret` | `string` or `null` | Placeholder `%secret%` can be used in command. Placeholder will be replaced with this value and will not appear in any logs. |
| `$env` | `array` or `null` | Array of environment variables: `runLocally('echo $KEY', env: ['key' => 'value']);` |
| `$forceOutput` | `bool` or `null` | Print command output in real-time. |
| `$nothrow` | `bool` or `null` | Don't throw an exception of non-zero exit code. |
| `$shell` | `string` or `null` | Shell to run in. Default is `bash -s`. |

## test()

```php
test(string $command): bool
```

Run test command.
Example:

```php
if (test('[ -d {{release_path}} ]')) {
...
}
```



## testLocally()

```php
testLocally(string $command): bool
```

Run test command locally.
Example:

    testLocally('[ -d {{local_release_path}} ]')



## on()

```php
on($hosts, callable $callback): void
```

Iterate other hosts, allowing to call run a func in callback.

```php
on(select('stage=prod, role=db'), function ($host) {
    ...
});
```

```php
on(host('example.org'), function ($host) {
    ...
});
```

```php
on(Deployer::get()->hosts, function ($host) {
    ...
});
```



## invoke()

```php
invoke(string $taskName): void
```

Runs a task.
```php
invoke('deploy:symlink');
```



## upload()

```php
upload($source, string $destination, array $config = []): void
```
Download .txt
gitextract_pd_hfplz/

├── .gitattributes
├── .github/
│   ├── DISCUSSION_TEMPLATE/
│   │   └── bugs.yml
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   └── config.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── labeler.yml
│   └── workflows/
│       ├── check.yml
│       ├── docker.yml
│       ├── docs-sync.yml
│       ├── docs.yml
│       ├── labeler.yml
│       ├── lint.yml
│       ├── release.yml
│       ├── stale.yml
│       └── test.yml
├── .gitignore
├── .php-cs-fixer.dist.php
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── bin/
│   ├── build
│   ├── dep
│   └── docgen
├── composer.json
├── contrib/
│   ├── bugsnag.php
│   ├── cachetool.php
│   ├── chatwork.php
│   ├── cimonitor.php
│   ├── cloudflare.php
│   ├── cpanel.php
│   ├── crontab.php
│   ├── directadmin.php
│   ├── discord.php
│   ├── grafana.php
│   ├── hangouts.php
│   ├── hipchat.php
│   ├── ispmanager.php
│   ├── mattermost.php
│   ├── ms-teams.php
│   ├── newrelic.php
│   ├── npm.php
│   ├── ntfy.php
│   ├── phinx.php
│   ├── php-fpm.php
│   ├── rabbit.php
│   ├── raygun.php
│   ├── rocketchat.php
│   ├── rollbar.php
│   ├── rsync.php
│   ├── sentry.php
│   ├── slack.php
│   ├── supervisord-monitor.php
│   ├── telegram.php
│   ├── webpack_encore.php
│   ├── workplace.php
│   ├── yammer.php
│   └── yarn.php
├── docs/
│   ├── KNOWN_BUGS.md
│   ├── UPGRADE.md
│   ├── api.md
│   ├── avoid-php-fpm-reloading.md
│   ├── basics.md
│   ├── ci-cd.md
│   ├── cli.md
│   ├── contrib/
│   │   ├── README.md
│   │   ├── bugsnag.md
│   │   ├── cachetool.md
│   │   ├── chatwork.md
│   │   ├── cimonitor.md
│   │   ├── cloudflare.md
│   │   ├── cpanel.md
│   │   ├── crontab.md
│   │   ├── directadmin.md
│   │   ├── discord.md
│   │   ├── grafana.md
│   │   ├── hangouts.md
│   │   ├── hipchat.md
│   │   ├── ispmanager.md
│   │   ├── mattermost.md
│   │   ├── ms-teams.md
│   │   ├── newrelic.md
│   │   ├── npm.md
│   │   ├── ntfy.md
│   │   ├── phinx.md
│   │   ├── php-fpm.md
│   │   ├── rabbit.md
│   │   ├── raygun.md
│   │   ├── rocketchat.md
│   │   ├── rollbar.md
│   │   ├── rsync.md
│   │   ├── sentry.md
│   │   ├── slack.md
│   │   ├── supervisord-monitor.md
│   │   ├── telegram.md
│   │   ├── webpack_encore.md
│   │   ├── workplace.md
│   │   ├── yammer.md
│   │   └── yarn.md
│   ├── getting-started.md
│   ├── hosts.md
│   ├── installation.md
│   ├── recipe/
│   │   ├── README.md
│   │   ├── cakephp.md
│   │   ├── codeigniter.md
│   │   ├── codeigniter4.md
│   │   ├── common.md
│   │   ├── composer.md
│   │   ├── contao.md
│   │   ├── craftcms.md
│   │   ├── deploy/
│   │   │   ├── check_remote.md
│   │   │   ├── cleanup.md
│   │   │   ├── clear_paths.md
│   │   │   ├── copy_dirs.md
│   │   │   ├── env.md
│   │   │   ├── info.md
│   │   │   ├── lock.md
│   │   │   ├── push.md
│   │   │   ├── release.md
│   │   │   ├── rollback.md
│   │   │   ├── setup.md
│   │   │   ├── shared.md
│   │   │   ├── symlink.md
│   │   │   ├── update_code.md
│   │   │   ├── vendors.md
│   │   │   └── writable.md
│   │   ├── drupal7.md
│   │   ├── drupal8.md
│   │   ├── flow_framework.md
│   │   ├── fuelphp.md
│   │   ├── joomla.md
│   │   ├── laravel.md
│   │   ├── magento.md
│   │   ├── magento2.md
│   │   ├── pimcore.md
│   │   ├── prestashop.md
│   │   ├── provision/
│   │   │   ├── databases.md
│   │   │   ├── nodejs.md
│   │   │   ├── php.md
│   │   │   ├── user.md
│   │   │   └── website.md
│   │   ├── provision.md
│   │   ├── shopware.md
│   │   ├── silverstripe.md
│   │   ├── spiral.md
│   │   ├── statamic.md
│   │   ├── sulu.md
│   │   ├── symfony.md
│   │   ├── typo3.md
│   │   ├── wordpress.md
│   │   ├── yii.md
│   │   └── zend_framework.md
│   ├── selector.md
│   ├── sidebar.js
│   ├── tasks.md
│   └── yaml.md
├── phpstan.neon
├── phpunit.xml
├── recipe/
│   ├── cakephp.php
│   ├── codeigniter.php
│   ├── codeigniter4.php
│   ├── common.php
│   ├── composer.php
│   ├── contao.php
│   ├── craftcms.php
│   ├── deploy/
│   │   ├── check_remote.php
│   │   ├── cleanup.php
│   │   ├── clear_paths.php
│   │   ├── copy_dirs.php
│   │   ├── env.php
│   │   ├── info.php
│   │   ├── lock.php
│   │   ├── push.php
│   │   ├── release.php
│   │   ├── rollback.php
│   │   ├── setup.php
│   │   ├── shared.php
│   │   ├── symlink.php
│   │   ├── update_code.php
│   │   ├── vendors.php
│   │   └── writable.php
│   ├── drupal7.php
│   ├── drupal8.php
│   ├── flow_framework.php
│   ├── fuelphp.php
│   ├── joomla.php
│   ├── laravel.php
│   ├── magento.php
│   ├── magento2.php
│   ├── pimcore.php
│   ├── prestashop.php
│   ├── provision/
│   │   ├── 404.html
│   │   ├── Caddyfile
│   │   ├── databases.php
│   │   ├── nodejs.php
│   │   ├── php.php
│   │   ├── user.php
│   │   └── website.php
│   ├── provision.php
│   ├── shopware.php
│   ├── silverstripe.php
│   ├── spiral.php
│   ├── statamic.php
│   ├── sulu.php
│   ├── symfony.php
│   ├── typo3.php
│   ├── wordpress.php
│   ├── yii.php
│   └── zend_framework.php
├── src/
│   ├── Collection/
│   │   └── Collection.php
│   ├── Command/
│   │   ├── BlackjackCommand.php
│   │   ├── CommandCommon.php
│   │   ├── ConfigCommand.php
│   │   ├── CustomOption.php
│   │   ├── InitCommand.php
│   │   ├── MainCommand.php
│   │   ├── RunCommand.php
│   │   ├── SelectCommand.php
│   │   ├── SshCommand.php
│   │   ├── TreeCommand.php
│   │   └── WorkerCommand.php
│   ├── Component/
│   │   ├── PharUpdate/
│   │   │   ├── Console/
│   │   │   │   ├── Command.php
│   │   │   │   └── Helper.php
│   │   │   ├── Exception/
│   │   │   │   ├── Exception.php
│   │   │   │   ├── ExceptionInterface.php
│   │   │   │   ├── FileException.php
│   │   │   │   ├── InvalidArgumentException.php
│   │   │   │   └── LogicException.php
│   │   │   ├── Manager.php
│   │   │   ├── Manifest.php
│   │   │   ├── Update.php
│   │   │   └── Version/
│   │   │       ├── Builder.php
│   │   │       ├── Comparator.php
│   │   │       ├── Dumper.php
│   │   │       ├── Exception/
│   │   │       │   ├── InvalidIdentifierException.php
│   │   │       │   ├── InvalidNumberException.php
│   │   │       │   ├── InvalidStringRepresentationException.php
│   │   │       │   └── VersionException.php
│   │   │       ├── Parser.php
│   │   │       ├── Validator.php
│   │   │       └── Version.php
│   │   └── Pimple/
│   │       ├── Container.php
│   │       └── Exception/
│   │           ├── ExpectedInvokableException.php
│   │           ├── FrozenServiceException.php
│   │           ├── InvalidServiceIdentifierException.php
│   │           └── UnknownIdentifierException.php
│   ├── Configuration.php
│   ├── Deployer.php
│   ├── Documentation/
│   │   ├── ApiGen.php
│   │   ├── DocConfig.php
│   │   ├── DocGen.php
│   │   ├── DocRecipe.php
│   │   └── DocTask.php
│   ├── Exception/
│   │   ├── ConfigurationException.php
│   │   ├── Exception.php
│   │   ├── GracefulShutdownException.php
│   │   ├── HttpieException.php
│   │   ├── RunException.php
│   │   ├── TimeoutException.php
│   │   └── WillAskUser.php
│   ├── Executor/
│   │   ├── Master.php
│   │   ├── Messenger.php
│   │   ├── Planner.php
│   │   ├── Response.php
│   │   ├── Server.php
│   │   └── Worker.php
│   ├── Host/
│   │   ├── Host.php
│   │   ├── HostCollection.php
│   │   ├── Localhost.php
│   │   └── Range.php
│   ├── Importer/
│   │   └── Importer.php
│   ├── Logger/
│   │   ├── Handler/
│   │   │   ├── FileHandler.php
│   │   │   ├── HandlerInterface.php
│   │   │   └── NullHandler.php
│   │   └── Logger.php
│   ├── ProcessRunner/
│   │   ├── Printer.php
│   │   └── ProcessRunner.php
│   ├── Selector/
│   │   └── Selector.php
│   ├── Ssh/
│   │   ├── IOArguments.php
│   │   ├── RunParams.php
│   │   └── SshClient.php
│   ├── Support/
│   │   ├── ObjectProxy.php
│   │   ├── Reporter.php
│   │   └── helpers.php
│   ├── Task/
│   │   ├── Context.php
│   │   ├── GroupTask.php
│   │   ├── ScriptManager.php
│   │   ├── Task.php
│   │   └── TaskCollection.php
│   ├── Utility/
│   │   ├── Httpie.php
│   │   └── Rsync.php
│   ├── functions.php
│   └── schema.json
└── tests/
    ├── bootstrap.php
    ├── fixtures/
    │   ├── project/
    │   │   └── uploaded.html
    │   └── repository/
    │       ├── README.md
    │       ├── composer.json
    │       └── uploads/
    │           └── poem.txt
    ├── joy/
    │   ├── HostDefaultConfigTest.php
    │   ├── JoyTest.php
    │   └── OnFuncTest.php
    ├── legacy/
    │   ├── AbstractTest.php
    │   ├── CurrentPathTest.php
    │   ├── DeployTest.php
    │   ├── EnvTest.php
    │   ├── NamedArgumentsTest.php
    │   ├── OncePerNodeTest.php
    │   ├── OnceTest.php
    │   ├── ParallelTest.php
    │   ├── SelectTest.php
    │   ├── UpdateCodeTest.php
    │   ├── YamlTest.php
    │   └── recipe/
    │       ├── deploy.php
    │       ├── deploy.yaml
    │       ├── env.php
    │       ├── once.php
    │       ├── once_per_node.php
    │       ├── parallel.php
    │       ├── select.php
    │       └── update_code.php
    ├── phpstan-baseline.neon
    └── src/
        ├── Collection/
        │   └── CollectionTest.php
        ├── Command/
        │   └── BlackjackCommandTest.php
        ├── Component/
        │   └── Pimple/
        │       └── PimpleTest.php
        ├── Configuration/
        │   └── ConfigurationTest.php
        ├── DeployerTest.php
        ├── FunctionsTest.php
        ├── Host/
        │   ├── ConfigurationTest.php
        │   ├── HostTest.php
        │   └── RangeTest.php
        ├── Importer/
        │   └── ImporterTest.php
        ├── Selector/
        │   └── SelectorTest.php
        ├── Ssh/
        │   └── IOArgumentsTest.php
        ├── Support/
        │   ├── HelpersTest.php
        │   └── ObjectProxyTest.php
        └── Task/
            ├── ContextTest.php
            ├── ScriptManagerTest.php
            └── TaskTest.php
Download .txt
SYMBOL INDEX (739 symbols across 127 files)

FILE: contrib/cpanel.php
  function getCpanel (line 146) | function getCpanel()
  function getDomainInfo (line 182) | function getDomainInfo()

FILE: contrib/crontab.php
  function setRemoteCrontab (line 129) | function setRemoteCrontab(array $lines): void
  function getRemoteCrontab (line 147) | function getRemoteCrontab(): array

FILE: contrib/directadmin.php
  function getDirectAdminConfig (line 31) | function getDirectAdminConfig()
  function DirectAdmin (line 53) | function DirectAdmin(string $action, array $data = [])

FILE: contrib/ispmanager.php
  function ispmanagerRequest (line 697) | function ispmanagerRequest($method, $requestData)
  function ispmanagerAuthRequest (line 725) | function ispmanagerAuthRequest($url, $login, $pass)
  function prepareRequest (line 746) | function prepareRequest($requestData)
  function generatePassword (line 766) | function generatePassword($lenght)

FILE: contrib/phinx.php
  function phinx_get_cmd (line 107) | function phinx_get_cmd($cmdName, $conf)
  function phinx_get_allowed_config (line 131) | function phinx_get_allowed_config($allowedOptions)

FILE: contrib/sentry.php
  function getPreviousReleaseRevision (line 187) | function getPreviousReleaseRevision()
  function getCurrentReleaseRevision (line 209) | function getCurrentReleaseRevision()
  function getReleaseGitRef (line 225) | function getReleaseGitRef(): Closure
  function getGitCommitsRefs (line 248) | function getGitCommitsRefs(): Closure

FILE: contrib/slack.php
  function checkSlackAnswer (line 90) | function checkSlackAnswer($result)

FILE: contrib/supervisord-monitor.php
  function supervisordCheckConfig (line 94) | function supervisordCheckConfig()
  function supervisordGetBasicAuthToken (line 111) | function supervisordGetBasicAuthToken()
  function supervisordIsAuthenticated (line 116) | function supervisordIsAuthenticated()
  function supervisordControlAction (line 126) | function supervisordControlAction($name, $action = 'stop')

FILE: recipe/codeigniter4.php
  function spark (line 46) | function spark($command, $options = [])
  function codeigniter4_version_compare (line 83) | function codeigniter4_version_compare($version, $comparator)

FILE: recipe/craftcms.php
  function craft (line 37) | function craft($command, $options = [])

FILE: recipe/laravel.php
  function artisan (line 39) | function artisan($command, $options = [])
  function laravel_version_compare (line 77) | function laravel_version_compare($version, $comparator)

FILE: recipe/magento2.php
  function magentoDeployAssetsSplit (line 230) | function magentoDeployAssetsSplit(string $area)

FILE: recipe/shopware.php
  function getPlugins (line 122) | function getPlugins(): array

FILE: recipe/spiral.php
  function command (line 26) | function command(string $command, array $options = []): \Closure
  function rr (line 43) | function rr(string $command, array $options = []): \Closure

FILE: src/Collection/Collection.php
  class Collection (line 16) | class Collection implements Countable, IteratorAggregate
    method all (line 20) | public function all(): array
    method get (line 25) | public function get(string $name): mixed
    method has (line 33) | public function has(string $name): bool
    method set (line 38) | public function set(string $name, mixed $object)
    method remove (line 43) | public function remove(string $name): void
    method count (line 51) | public function count(): int
    method select (line 56) | public function select(callable $callback): array
    method getIterator (line 72) | #[\ReturnTypeWillChange]
    method notFound (line 78) | protected function notFound(string $name): \InvalidArgumentException

FILE: src/Command/BlackjackCommand.php
  class BlackjackCommand (line 21) | class BlackjackCommand extends Command
    method __construct (line 35) | public function __construct()
    method execute (line 41) | protected function execute(Input $input, Output $output): int
    method newDeck (line 232) | private function newDeck(): array
    method handValue (line 247) | public static function handValue(array $hand): int
    method print (line 305) | private function print(string $text = "")
    method printHand (line 310) | private function printHand(array $hand, int $offset = 1)
    method printWhiskey (line 348) | private function printWhiskey(int $whiskeyLevel)

FILE: src/Command/CommandCommon.php
  type CommandCommon (line 16) | trait CommandCommon
    method telemetry (line 24) | protected function telemetry(array $data = []): void

FILE: src/Command/ConfigCommand.php
  class ConfigCommand (line 22) | class ConfigCommand extends SelectCommand
    method __construct (line 24) | public function __construct(Deployer $deployer)
    method configure (line 30) | protected function configure()
    method execute (line 37) | protected function execute(Input $input, Output $output): int

FILE: src/Command/CustomOption.php
  type CustomOption (line 15) | trait CustomOption
    method applyOverrides (line 21) | protected function applyOverrides(array $hosts, array $options)
    method castValueToPhpType (line 41) | protected function castValueToPhpType($value)

FILE: src/Command/InitCommand.php
  class InitCommand (line 22) | class InitCommand extends Command
    method configure (line 26) | protected function configure()
    method execute (line 34) | protected function execute(InputInterface $input, OutputInterface $out...
    method php (line 161) | private function php(string $template, string $project, string $reposi...
    method yaml (line 194) | private function yaml(string $template, string $project, string $repos...
    method getAdditionalConfigs (line 224) | private function getAdditionalConfigs(string $template): string
    method recipes (line 241) | private function recipes(): array

FILE: src/Command/MainCommand.php
  class MainCommand (line 24) | class MainCommand extends SelectCommand
    method __construct (line 29) | public function __construct(string $name, ?string $description, Deploy...
    method configure (line 37) | protected function configure()
    method execute (line 88) | protected function execute(Input $input, Output $output): int
    method checkUpdates (line 160) | private function checkUpdates()
    method showBanner (line 169) | private function showBanner()
    method complete (line 186) | public function complete(CompletionInput $input, CompletionSuggestions...

FILE: src/Command/RunCommand.php
  class RunCommand (line 27) | class RunCommand extends SelectCommand
    method __construct (line 31) | public function __construct(Deployer $deployer)
    method configure (line 37) | protected function configure()
    method execute (line 59) | protected function execute(Input $input, Output $output): int

FILE: src/Command/SelectCommand.php
  class SelectCommand (line 27) | abstract class SelectCommand extends Command
    method __construct (line 34) | public function __construct(string $name, Deployer $deployer)
    method configure (line 40) | protected function configure()
    method selectHosts (line 48) | protected function selectHosts(Input $input, Output $output): array
    method complete (line 97) | public function complete(CompletionInput $input, CompletionSuggestions...

FILE: src/Command/SshCommand.php
  class SshCommand (line 28) | class SshCommand extends Command
    method __construct (line 37) | public function __construct(Deployer $deployer)
    method configure (line 44) | protected function configure()
    method execute (line 53) | protected function execute(InputInterface $input, OutputInterface $out...
    method complete (line 107) | public function complete(CompletionInput $input, CompletionSuggestions...

FILE: src/Command/TreeCommand.php
  class TreeCommand (line 22) | class TreeCommand extends Command
    method __construct (line 45) | public function __construct(Deployer $deployer)
    method configure (line 53) | protected function configure()
    method execute (line 62) | protected function execute(Input $input, Output $output): int
    method buildTree (line 73) | private function buildTree(string $taskName)
    method createTreeFromTaskName (line 78) | private function createTreeFromTaskName(string $taskName, string $post...
    method addTaskToTree (line 133) | private function addTaskToTree(string $taskName, bool $isLast = false)
    method outputTree (line 143) | private function outputTree(string $taskName)
    method complete (line 173) | public function complete(CompletionInput $input, CompletionSuggestions...

FILE: src/Command/WorkerCommand.php
  class WorkerCommand (line 22) | class WorkerCommand extends MainCommand
    method __construct (line 24) | public function __construct(Deployer $deployer)
    method configure (line 30) | protected function configure()
    method execute (line 39) | protected function execute(InputInterface $input, OutputInterface $out...

FILE: src/Component/PharUpdate/Console/Command.php
  class Command (line 19) | class Command extends Base
    method __construct (line 46) | public function __construct(string $name, bool $disable = false)
    method setManifestUri (line 58) | public function setManifestUri(string $uri)
    method setRunningFile (line 68) | public function setRunningFile(string $file): void
    method configure (line 76) | protected function configure()
    method execute (line 105) | protected function execute(InputInterface $input, OutputInterface $out...

FILE: src/Component/PharUpdate/Console/Helper.php
  class Helper (line 16) | class Helper extends Base
    method getManager (line 25) | public function getManager(string $uri): Manager
    method getName (line 30) | public function getName(): string

FILE: src/Component/PharUpdate/Exception/Exception.php
  class Exception (line 12) | class Exception extends \Exception implements ExceptionInterface
    method create (line 19) | public static function create(string $format, $value = null): self
    method lastError (line 31) | public static function lastError(): self

FILE: src/Component/PharUpdate/Exception/ExceptionInterface.php
  type ExceptionInterface (line 12) | interface ExceptionInterface {}

FILE: src/Component/PharUpdate/Exception/FileException.php
  class FileException (line 12) | class FileException extends Exception {}

FILE: src/Component/PharUpdate/Exception/InvalidArgumentException.php
  class InvalidArgumentException (line 12) | class InvalidArgumentException extends Exception {}

FILE: src/Component/PharUpdate/Exception/LogicException.php
  class LogicException (line 12) | class LogicException extends Exception {}

FILE: src/Component/PharUpdate/Manager.php
  class Manager (line 16) | class Manager
    method __construct (line 37) | public function __construct(Manifest $manifest)
    method getManifest (line 47) | public function getManifest(): Manifest
    method getRunningFile (line 57) | public function getRunningFile(): string
    method setRunningFile (line 74) | public function setRunningFile(string $file): void
    method update (line 95) | public function update($version, bool $major = false, bool $pre = fals...

FILE: src/Component/PharUpdate/Manifest.php
  class Manifest (line 16) | class Manifest
    method __construct (line 30) | public function __construct(array $updates = [])
    method findRecent (line 42) | public function findRecent(Version $version, bool $major = false, bool...
    method getUpdates (line 75) | public function getUpdates(): array
    method load (line 85) | public static function load(string $json): self
    method loadFile (line 95) | public static function loadFile(string $file): self
    method create (line 107) | private static function create(array $decoded): self

FILE: src/Component/PharUpdate/Update.php
  class Update (line 20) | class Update
    method __construct (line 74) | public function __construct(
    method copyTo (line 96) | public function copyTo(string $file): void
    method deleteFile (line 136) | public function deleteFile(): void
    method getFile (line 172) | public function getFile(): ?string
    method getName (line 226) | public function getName(): string
    method getPublicKey (line 234) | public function getPublicKey(): string
    method getSha1 (line 242) | public function getSha1(): string
    method getUrl (line 250) | public function getUrl(): string
    method getVersion (line 258) | public function getVersion(): Version
    method isNewer (line 270) | public function isNewer(Version $version): bool

FILE: src/Component/PharUpdate/Version/Builder.php
  class Builder (line 15) | class Builder extends Version
    method clearBuild (line 20) | public function clearBuild(): void
    method clearPreRelease (line 28) | public function clearPreRelease(): void
    method create (line 38) | public static function create(): Builder
    method getVersion (line 48) | public function getVersion(): Version
    method importComponents (line 66) | public function importComponents(array $components): self
    method importString (line 108) | public function importString(string $version): self
    method importVersion (line 120) | public function importVersion(Version $version): self
    method incrementMajor (line 138) | public function incrementMajor(int $amount = 1): self
    method incrementMinor (line 155) | public function incrementMinor(int $amount = 1): self
    method incrementPatch (line 170) | public function incrementPatch(int $amount = 1): self
    method setBuild (line 186) | public function setBuild(array $identifiers): self
    method setMajor (line 208) | public function setMajor(int $number): self
    method setMinor (line 228) | public function setMinor(int $number): self
    method setPatch (line 248) | public function setPatch(int $number): self
    method setPreRelease (line 268) | public function setPreRelease(array $identifiers): self

FILE: src/Component/PharUpdate/Version/Comparator.php
  class Comparator (line 12) | class Comparator
    method compareTo (line 41) | public static function compareTo(Version $left, Version $right)
    method isEqualTo (line 75) | public static function isEqualTo(Version $left, Version $right)
    method isGreaterThan (line 89) | public static function isGreaterThan(Version $left, Version $right)
    method isLessThan (line 103) | public static function isLessThan(Version $left, Version $right)
    method compareIdentifiers (line 120) | public static function compareIdentifiers(array $left, array $right)

FILE: src/Component/PharUpdate/Version/Dumper.php
  class Dumper (line 12) | class Dumper
    method toComponents (line 21) | public static function toComponents(Version $version)
    method toString (line 39) | public static function toString(Version $version)

FILE: src/Component/PharUpdate/Version/Exception/InvalidIdentifierException.php
  class InvalidIdentifierException (line 12) | class InvalidIdentifierException extends VersionException
    method __construct (line 26) | public function __construct(string $identifier)
    method getIdentifier (line 43) | public function getIdentifier(): string

FILE: src/Component/PharUpdate/Version/Exception/InvalidNumberException.php
  class InvalidNumberException (line 12) | class InvalidNumberException extends VersionException
    method __construct (line 26) | public function __construct($number)
    method getNumber (line 43) | public function getNumber()

FILE: src/Component/PharUpdate/Version/Exception/InvalidStringRepresentationException.php
  class InvalidStringRepresentationException (line 12) | class InvalidStringRepresentationException extends VersionException
    method __construct (line 26) | public function __construct(string $version)
    method getVersion (line 43) | public function getVersion(): string

FILE: src/Component/PharUpdate/Version/Exception/VersionException.php
  class VersionException (line 14) | class VersionException extends Exception {}

FILE: src/Component/PharUpdate/Version/Parser.php
  class Parser (line 14) | class Parser
    method toBuilder (line 48) | public static function toBuilder(string $version): Builder
    method toComponents (line 65) | public static function toComponents(string $version): array
    method toVersion (line 105) | public static function toVersion(string $version): Version

FILE: src/Component/PharUpdate/Version/Validator.php
  class Validator (line 12) | class Validator
    method isIdentifier (line 31) | public static function isIdentifier(string $identifier): bool
    method isNumber (line 43) | public static function isNumber(int $number): bool
    method isVersion (line 55) | public static function isVersion(string $version): bool

FILE: src/Component/PharUpdate/Version/Version.php
  class Version (line 12) | class Version
    method __construct (line 58) | public function __construct(
    method getBuild (line 77) | public function getBuild(): array
    method getMajor (line 87) | public function getMajor(): int
    method getMinor (line 97) | public function getMinor(): int
    method getPatch (line 107) | public function getPatch(): int
    method getPreRelease (line 117) | public function getPreRelease(): array
    method isStable (line 127) | public function isStable(): bool
    method __toString (line 137) | public function __toString(): string

FILE: src/Component/Pimple/Container.php
  class Container (line 23) | class Container implements \ArrayAccess
    method __construct (line 57) | public function __construct(array $values = [])
    method offsetSet (line 82) | #[\ReturnTypeWillChange]
    method offsetGet (line 103) | #[\ReturnTypeWillChange]
    method offsetExists (line 140) | #[\ReturnTypeWillChange]
    method offsetUnset (line 152) | #[\ReturnTypeWillChange]
    method factory (line 173) | public function factory(callable $callable)
    method protect (line 195) | public function protect(callable $callable)
    method raw (line 215) | public function raw(string $id)
    method extend (line 244) | public function extend(string $id, callable $callable)
    method keys (line 285) | public function keys()

FILE: src/Component/Pimple/Exception/ExpectedInvokableException.php
  class ExpectedInvokableException (line 20) | class ExpectedInvokableException extends \InvalidArgumentException imple...

FILE: src/Component/Pimple/Exception/FrozenServiceException.php
  class FrozenServiceException (line 20) | class FrozenServiceException extends \RuntimeException implements Contai...
    method __construct (line 25) | public function __construct(string $id)

FILE: src/Component/Pimple/Exception/InvalidServiceIdentifierException.php
  class InvalidServiceIdentifierException (line 20) | class InvalidServiceIdentifierException extends \InvalidArgumentExceptio...
    method __construct (line 25) | public function __construct(string $id)

FILE: src/Component/Pimple/Exception/UnknownIdentifierException.php
  class UnknownIdentifierException (line 20) | class UnknownIdentifierException extends \InvalidArgumentException imple...
    method __construct (line 25) | public function __construct(string $id)

FILE: src/Configuration.php
  class Configuration (line 20) | class Configuration implements \ArrayAccess
    method __construct (line 25) | public function __construct(?Configuration $parent = null)
    method update (line 30) | public function update(array $values): void
    method bind (line 35) | public function bind(Configuration $parent): void
    method set (line 40) | public function set(string $name, mixed $value): void
    method has (line 45) | public function has(string $name): bool
    method hasOwn (line 57) | public function hasOwn(string $name): bool
    method add (line 62) | public function add(string $name, array $array): void
    method get (line 75) | public function get(string $name, mixed $default = null): mixed
    method fetch (line 103) | protected function fetch(string $name): mixed
    method parse (line 114) | public function parse(mixed $value): mixed
    method keys (line 126) | public function keys(): array
    method offsetExists (line 135) | #[\ReturnTypeWillChange]
    method offsetGet (line 145) | #[\ReturnTypeWillChange]
    method offsetSet (line 155) | #[\ReturnTypeWillChange]
    method offsetUnset (line 164) | #[\ReturnTypeWillChange]
    method load (line 170) | public function load(): void
    method save (line 186) | public function save(): void
    method persist (line 202) | public function persist(): array

FILE: src/Deployer.php
  class Deployer (line 73) | class Deployer extends Container
    method __construct (line 77) | public function __construct(Application $console)
    method get (line 185) | public static function get(): self
    method init (line 190) | public function init(): void
    method addTaskCommands (line 213) | public function addTaskCommands(): void
    method __get (line 223) | public function __get(string $name): mixed
    method __set (line 232) | public function __set(string $name, mixed $value): void
    method getConsole (line 237) | public function getConsole(): Application
    method getHelper (line 242) | public function getHelper(string $name): Console\Helper\HelperInterface
    method run (line 247) | public static function run(string $version, ?string $deployFile): void
    method printException (line 301) | public static function printException(OutputInterface $output, Throwab...
    method isWorker (line 322) | public static function isWorker(): bool
    method masterCall (line 330) | public static function masterCall(Host $host, string $func, mixed ...$...
    method isPharArchive (line 347) | public static function isPharArchive(): bool

FILE: src/Documentation/ApiGen.php
  class ApiGen (line 13) | class ApiGen
    method parse (line 20) | public function parse(string $source): void
    method markdown (line 91) | public function markdown(): string

FILE: src/Documentation/DocConfig.php
  class DocConfig (line 13) | class DocConfig

FILE: src/Documentation/DocGen.php
  class DocGen (line 18) | class DocGen
    method __construct (line 29) | public function __construct(string $root)
    method parse (line 34) | public function parse(string $source): void
    method gen (line 48) | public function gen(string $destination): ?string
    method generateRecipesIndex (line 321) | public function generateRecipesIndex(string $destination)
    method generateContribIndex (line 336) | public function generateContribIndex(string $destination)
  function trim_comment (line 352) | function trim_comment(string $line): string
  function indent (line 357) | function indent(string $text): string
  function php_to_md (line 364) | function php_to_md(string $file): string
  function title (line 369) | function title(string $s): string
  function anchor (line 374) | function anchor(string $s): string
  function remove_text_emoji (line 379) | function remove_text_emoji(string $text): string
  function add_tailing_dot (line 384) | function add_tailing_dot(string $sentence): string
  function recipe_to_md_link (line 395) | function recipe_to_md_link(string $recipe): string
  function is_framework_recipe (line 402) | function is_framework_recipe(DocRecipe $recipe): bool
  function framework_brand_name (line 408) | function framework_brand_name(string $brandName): string

FILE: src/Documentation/DocRecipe.php
  class DocRecipe (line 13) | class DocRecipe
    method __construct (line 40) | public function __construct(string $recipeName, string $recipePath)
    method parse (line 49) | public function parse(string $content)

FILE: src/Documentation/DocTask.php
  class DocTask (line 13) | class DocTask
    method mdLink (line 40) | public function mdLink(): string

FILE: src/Exception/ConfigurationException.php
  class ConfigurationException (line 13) | class ConfigurationException extends \RuntimeException {}

FILE: src/Exception/Exception.php
  class Exception (line 15) | class Exception extends \Exception
    method __construct (line 30) | public function __construct(string $message = "", int $code = 0, ?Thro...
    method setTaskSourceLocation (line 45) | public static function setTaskSourceLocation(string $filepath): void
    method getTaskFilename (line 50) | public function getTaskFilename(): string
    method getTaskLineNumber (line 55) | public function getTaskLineNumber(): int
    method setTaskFilename (line 60) | public function setTaskFilename(string $taskFilename): void
    method setTaskLineNumber (line 65) | public function setTaskLineNumber(int $taskLineNumber): void

FILE: src/Exception/GracefulShutdownException.php
  class GracefulShutdownException (line 24) | class GracefulShutdownException extends Exception

FILE: src/Exception/HttpieException.php
  class HttpieException (line 13) | class HttpieException extends \RuntimeException {}

FILE: src/Exception/RunException.php
  class RunException (line 16) | class RunException extends Exception
    method __construct (line 39) | public function __construct(
    method getHost (line 56) | public function getHost(): Host
    method getCommand (line 61) | public function getCommand(): string
    method getExitCode (line 66) | public function getExitCode(): int
    method getExitCodeText (line 71) | public function getExitCodeText(): string
    method getOutput (line 76) | public function getOutput(): string
    method getErrorOutput (line 81) | public function getErrorOutput(): string

FILE: src/Exception/TimeoutException.php
  class TimeoutException (line 13) | class TimeoutException extends Exception
    method __construct (line 15) | public function __construct(

FILE: src/Exception/WillAskUser.php
  class WillAskUser (line 13) | class WillAskUser extends Exception
    method __construct (line 15) | public function __construct(string $message)

FILE: src/Executor/Master.php
  function spinner (line 27) | function spinner(string $message = ''): string
  class Master (line 33) | class Master
    method __construct (line 41) | public function __construct(
    method run (line 58) | public function run(array $tasks, array $hosts, ?Planner $plan = null)...
    method runTask (line 147) | private function runTask(Task $task, array $hosts): int
    method createProcess (line 244) | protected function createProcess(Host $host, Task $task, int $port): P...
    method allFinished (line 265) | protected function allFinished(array $processes): bool
    method gatherOutput (line 278) | protected function gatherOutput(array $processes, callable $callback):...
    method cumulativeExitCode (line 296) | protected function cumulativeExitCode(array $processes): int

FILE: src/Executor/Messenger.php
  class Messenger (line 22) | class Messenger
    method __construct (line 44) | public function __construct(Input $input, Output $output, Logger $logger)
    method startTask (line 51) | public function startTask(Task $task): void
    method endTask (line 69) | public function endTask(Task $task, bool $error = false): void
    method endOnHost (line 102) | public function endOnHost(Host $host): void
    method renderException (line 109) | public function renderException(Throwable $exception, Host $host): void

FILE: src/Executor/Planner.php
  class Planner (line 18) | class Planner
    method __construct (line 34) | public function __construct(OutputInterface $output, array $hosts)
    method commit (line 50) | public function commit(array $hosts, Task $task): void
    method render (line 66) | public function render()

FILE: src/Executor/Response.php
  class Response (line 13) | class Response
    method __construct (line 18) | public function __construct(int $status, mixed $body)
    method getStatus (line 24) | public function getStatus(): int
    method getBody (line 29) | public function getBody(): mixed

FILE: src/Executor/Server.php
  class Server (line 17) | class Server
    method __construct (line 40) | public function __construct($host, $port, OutputInterface $output)
    method checkRequiredExtensionsExists (line 48) | public static function checkRequiredExtensionsExists(): void
    method run (line 58) | public function run(): void
    method createServerSocket (line 97) | private function createServerSocket()
    method updatePort (line 111) | private function updatePort(): void
    method acceptNewConnections (line 122) | private function acceptNewConnections(): void
    method handleClientRequests (line 133) | private function handleClientRequests(): void
    method readClientRequest (line 150) | private function readClientRequest($clientSocket)
    method parseRequest (line 161) | private function parseRequest($request)
    method sendResponse (line 188) | private function sendResponse($clientSocket, Response $response)
    method closeClientSocket (line 199) | private function closeClientSocket($clientSocket, $key): void
    method afterRun (line 205) | public function afterRun(Closure $param): void
    method ticker (line 210) | public function ticker(Closure $param): void
    method router (line 215) | public function router(Closure $param)
    method stop (line 220) | public function stop(): void

FILE: src/Executor/Worker.php
  class Worker (line 22) | class Worker
    method __construct (line 26) | public function __construct(Deployer $deployer)
    method execute (line 31) | public function execute(Task $task, Host $host): int

FILE: src/Host/Host.php
  class Host (line 22) | class Host
    method __construct (line 29) | public function __construct(string $hostname)
    method __toString (line 40) | public function __toString(): string
    method config (line 45) | public function config(): Configuration
    method set (line 53) | public function set(string $name, $value): self
    method add (line 65) | public function add(string $name, array $value): self
    method has (line 71) | public function has(string $name): bool
    method hasOwn (line 76) | public function hasOwn(string $name): bool
    method get (line 85) | public function get(string $name, $default = null)
    method getAlias (line 90) | public function getAlias(): ?string
    method setTag (line 95) | public function setTag(string $tag): self
    method getTag (line 101) | public function getTag(): ?string
    method setHostname (line 106) | public function setHostname(string $hostname): self
    method getHostname (line 112) | public function getHostname(): ?string
    method setRemoteUser (line 117) | public function setRemoteUser(string $user): self
    method getRemoteUser (line 123) | public function getRemoteUser(): ?string
    method setPort (line 132) | public function setPort($port): self
    method getPort (line 141) | public function getPort()
    method setConfigFile (line 146) | public function setConfigFile(string $file): self
    method getConfigFile (line 152) | public function getConfigFile(): ?string
    method setIdentityFile (line 157) | public function setIdentityFile(string $file): self
    method getIdentityFile (line 163) | public function getIdentityFile(): ?string
    method setForwardAgent (line 168) | public function setForwardAgent(bool $on): self
    method getForwardAgent (line 174) | public function getForwardAgent(): ?bool
    method setSshMultiplexing (line 179) | public function setSshMultiplexing(bool $on): self
    method getSshMultiplexing (line 185) | public function getSshMultiplexing(): ?bool
    method setShell (line 190) | public function setShell(string $command): self
    method getShell (line 196) | public function getShell(): ?string
    method setShellPath (line 201) | public function setShellPath(string $path): self
    method getShellPath (line 207) | public function getShellPath(): ?string
    method setDeployPath (line 212) | public function setDeployPath(string $path): self
    method getDeployPath (line 218) | public function getDeployPath(): ?string
    method setLabels (line 223) | public function setLabels(array $labels): self
    method addLabels (line 229) | public function addLabels(array $labels): self
    method getLabels (line 236) | public function getLabels(): ?array
    method setSshArguments (line 241) | public function setSshArguments(array $args): self
    method getSshArguments (line 247) | public function getSshArguments(): ?array
    method setSshControlPath (line 252) | public function setSshControlPath(string $path): self
    method getSshControlPath (line 258) | public function getSshControlPath(): string
    method generateControlPath (line 263) | private function generateControlPath(): string
    method connectionString (line 281) | public function connectionString(): string
    method connectionOptionsString (line 289) | public function connectionOptionsString(): string
    method connectionOptionsArray (line 297) | public function connectionOptionsArray(): array

FILE: src/Host/HostCollection.php
  class HostCollection (line 19) | class HostCollection extends Collection
    method notFound (line 21) | protected function notFound(string $name): \InvalidArgumentException

FILE: src/Host/Localhost.php
  class Localhost (line 13) | class Localhost extends Host
    method __construct (line 15) | public function __construct(string $hostname = 'localhost')

FILE: src/Host/Range.php
  class Range (line 13) | class Range
    method expand (line 17) | public static function expand(array $hostnames): array
    method format (line 36) | private static function format(string $i, bool $zeroBased): string

FILE: src/Importer/Importer.php
  class Importer (line 34) | class Importer
    method import (line 48) | public static function import($paths)
    method hosts (line 87) | protected static function hosts(array $hosts)
    method config (line 103) | protected static function config(array $config)
    method tasks (line 110) | protected static function tasks(array $tasks)
    method after (line 220) | protected static function after(array $after)
    method before (line 233) | protected static function before(array $before)

FILE: src/Logger/Handler/FileHandler.php
  class FileHandler (line 13) | class FileHandler implements HandlerInterface
    method __construct (line 20) | public function __construct(string $filePath)
    method log (line 25) | public function log(string $message): void

FILE: src/Logger/Handler/HandlerInterface.php
  type HandlerInterface (line 13) | interface HandlerInterface
    method log (line 15) | public function log(string $message): void;

FILE: src/Logger/Handler/NullHandler.php
  class NullHandler (line 13) | class NullHandler implements HandlerInterface
    method log (line 15) | public function log(string $message): void {}

FILE: src/Logger/Logger.php
  class Logger (line 17) | class Logger
    method __construct (line 24) | public function __construct(HandlerInterface $handler)
    method log (line 29) | public function log(string $message): void
    method callback (line 34) | public function callback(Host $host): \Closure
    method printBuffer (line 41) | public function printBuffer(Host $host, string $type, string $buffer):...
    method writeln (line 48) | public function writeln(Host $host, string $type, string $line): void

FILE: src/ProcessRunner/Printer.php
  class Printer (line 16) | class Printer
    method __construct (line 20) | public function __construct(OutputInterface $output)
    method command (line 25) | public function command(Host $host, string $type, string $command): void
    method callback (line 38) | public function callback(Host $host, bool $forceOutput): callable
    method printBuffer (line 50) | public function printBuffer(string $type, Host $host, string $buffer):...
    method writeln (line 57) | public function writeln(string $type, Host $host, string $line): void

FILE: src/ProcessRunner/ProcessRunner.php
  class ProcessRunner (line 25) | class ProcessRunner
    method __construct (line 30) | public function __construct(Printer $pop, Logger $logger)
    method run (line 36) | public function run(Host $host, string $command, RunParams $params): s...

FILE: src/Selector/Selector.php
  class Selector (line 18) | class Selector
    method __construct (line 25) | public function __construct(HostCollection $hosts)
    method select (line 33) | public function select(string $selectExpression)
    method apply (line 47) | public static function apply(?array $conditions, Host $host): bool
    method compare (line 83) | private static function compare(string $op, $a, ?string $b): bool
    method parse (line 104) | public static function parse(string $expression): array

FILE: src/Ssh/IOArguments.php
  class IOArguments (line 17) | class IOArguments
    method collect (line 19) | public static function collect(InputInterface $input, OutputInterface ...
    method verbosity (line 58) | private static function verbosity(int $verbosity): string

FILE: src/Ssh/RunParams.php
  class RunParams (line 5) | class RunParams
    method __construct (line 7) | public function __construct(
    method with (line 20) | public function with(

FILE: src/Ssh/SshClient.php
  class SshClient (line 24) | class SshClient
    method __construct (line 30) | public function __construct(OutputInterface $output, Printer $pop, Log...
    method run (line 37) | public function run(Host $host, string $command, RunParams $params): s...

FILE: src/Support/ObjectProxy.php
  class ObjectProxy (line 13) | class ObjectProxy
    method __construct (line 20) | public function __construct(array $objects)
    method __call (line 25) | public function __call(string $name, array $arguments): self

FILE: src/Support/Reporter.php
  class Reporter (line 19) | class Reporter
    method report (line 21) | public static function report(array $stats): void

FILE: src/Support/helpers.php
  function array_flatten (line 13) | function array_flatten(array $array): array
  function array_merge_alternate (line 29) | function array_merge_alternate(array $original, array $override): array
  function env_stringify (line 57) | function env_stringify(array $array): string
  function is_closure (line 68) | function is_closure(mixed $var): bool
  function array_all (line 76) | function array_all(array $array, callable $predicate): bool
  function normalize_line_endings (line 89) | function normalize_line_endings(string $string): string
  function parse_home_dir (line 97) | function parse_home_dir(string $path): string
  function find_line_number (line 114) | function find_line_number(string $source, string $string): int
  function colorize_host (line 124) | function colorize_host(string $alias): string
  function escape_shell_argument (line 211) | function escape_shell_argument(string $argument): string
  function deployer_root (line 216) | function deployer_root(): string

FILE: src/Task/Context.php
  class Context (line 19) | class Context
    method __construct (line 28) | public function __construct(Host $host)
    method push (line 33) | public static function push(Context $context): void
    method has (line 38) | public static function has(): bool
    method get (line 43) | public static function get(): Context
    method pop (line 51) | public static function pop(): ?Context
    method required (line 64) | public static function required(string $callerName): void
    method getConfig (line 71) | public function getConfig(): Configuration
    method getHost (line 76) | public function getHost(): Host

FILE: src/Task/GroupTask.php
  class GroupTask (line 15) | class GroupTask extends Task
    method __construct (line 27) | public function __construct(string $name, array $group)
    method run (line 33) | public function run(Context $context): void
    method getGroup (line 45) | public function getGroup(): array
    method setGroup (line 50) | public function setGroup(array $group): void

FILE: src/Task/ScriptManager.php
  class ScriptManager (line 17) | class ScriptManager
    method __construct (line 32) | public function __construct(TaskCollection $tasks)
    method getTasks (line 42) | public function getTasks(string $name, ?string $startFrom = null, arra...
    method doGetTasks (line 81) | public function doGetTasks(string $name): array
    method getHooksEnabled (line 117) | public function getHooksEnabled(): bool
    method setHooksEnabled (line 122) | public function setHooksEnabled(bool $hooksEnabled): void

FILE: src/Task/Task.php
  class Task (line 15) | class Task
    method __construct (line 73) | public function __construct(string $name, ?callable $callback = null)
    method setCallback (line 82) | public function setCallback(callable $callback): void
    method run (line 87) | public function run(Context $context): void
    method getName (line 102) | public function getName(): string
    method __toString (line 107) | public function __toString(): string
    method getDescription (line 112) | public function getDescription(): ?string
    method desc (line 117) | public function desc(string $description): self
    method getSourceLocation (line 123) | public function getSourceLocation(): string
    method setSourceLocation (line 128) | public function setSourceLocation(string $path): void
    method saveSourceLocation (line 133) | public function saveSourceLocation(): void
    method once (line 144) | public function once(bool $once = true): self
    method isOnce (line 150) | public function isOnce(): bool
    method oncePerNode (line 159) | public function oncePerNode(bool $once = true): self
    method isOncePerNode (line 165) | public function isOncePerNode(): bool
    method hidden (line 173) | public function hidden(bool $hidden = true): self
    method isHidden (line 179) | public function isHidden(): bool
    method addBefore (line 187) | public function addBefore(string $task): self
    method addAfter (line 196) | public function addAfter(string $task): self
    method getBefore (line 202) | public function getBefore(): array
    method getAfter (line 207) | public function getAfter(): array
    method getLimit (line 212) | public function getLimit(): ?int
    method limit (line 217) | public function limit(?int $limit): self
    method select (line 223) | public function select(string $selector): self
    method getSelector (line 232) | public function getSelector(): ?array
    method addSelector (line 237) | public function addSelector(?array $newSelector): void
    method isVerbose (line 248) | public function isVerbose(): bool
    method verbose (line 253) | public function verbose(bool $verbose = true): self
    method isEnabled (line 259) | public function isEnabled(): bool
    method disable (line 264) | public function disable(): self
    method enable (line 270) | public function enable(): self

FILE: src/Task/TaskCollection.php
  class TaskCollection (line 19) | class TaskCollection extends Collection
    method notFound (line 21) | protected function notFound(string $name): \InvalidArgumentException
    method add (line 26) | public function add(Task $task): void

FILE: src/Utility/Httpie.php
  class Httpie (line 15) | class Httpie
    method __construct (line 24) | public function __construct()
    method get (line 34) | public static function get(string $url): Httpie
    method post (line 42) | public static function post(string $url): Httpie
    method patch (line 50) | public static function patch(string $url): Httpie
    method put (line 59) | public static function put(string $url): Httpie
    method delete (line 67) | public static function delete(string $url): Httpie
    method query (line 75) | public function query(array $params): self
    method header (line 81) | public function header(string $header, string $value): self
    method body (line 87) | public function body(string $body): self
    method jsonBody (line 96) | public function jsonBody(array $data): self
    method formBody (line 106) | public function formBody(array $data): self
    method setopt (line 119) | public function setopt(int $key, $value): self
    method nothrow (line 125) | public function nothrow(bool $on = true): self
    method send (line 131) | public function send(?array &$info = null): string
    method getJson (line 173) | public function getJson(): mixed

FILE: src/Utility/Rsync.php
  class Rsync (line 23) | class Rsync
    method __construct (line 34) | public function __construct(Printer $pop, OutputInterface $output)
    method call (line 47) | public function call(Host $host, $source, string $destination, array $...

FILE: src/functions.php
  function host (line 53) | function host(string ...$hostname): Host|ObjectProxy
  function localhost (line 90) | function localhost(string ...$hostnames): Localhost|ObjectProxy
  function currentHost (line 112) | function currentHost(): Host
  function select (line 128) | function select(string $selector): array
  function selectedHosts (line 138) | function selectedHosts(): array
  function import (line 158) | function import(string $file): void
  function desc (line 166) | function desc(?string $title = null): ?string
  function task (line 186) | function task(string $name, callable|array|null $body = null): Task
  function before (line 238) | function before(string $task, string|callable $do): ?Task
  function after (line 258) | function after(string $task, string|callable $do): ?Task
  function fail (line 279) | function fail(string $task, string|callable $do): ?Task
  function option (line 301) | function option(string $name, $shortcut = null, ?int $mode = null, strin...
  function cd (line 316) | function cd(string $path): void
  function become (line 336) | function become(string $user): \Closure
  function within (line 351) | function within(string $path, callable $callback): mixed
  function run (line 392) | function run(
  function runLocally (line 482) | function runLocally(
  function test (line 523) | function test(string $command): bool
  function testLocally (line 536) | function testLocally(string $command): bool
  function on (line 564) | function on($hosts, callable $callback): void
  function invoke (line 596) | function invoke(string $taskName): void
  function upload (line 628) | function upload($source, string $destination, array $config = []): void
  function download (line 649) | function download(string $source, string $destination, array $config = [...
  function info (line 666) | function info(string $message): void
  function warning (line 674) | function warning(string $message): void
  function writeln (line 688) | function writeln(string $message, int $options = 0): void
  function parse (line 697) | function parse(string $value): string
  function set (line 707) | function set(string $name, $value): void
  function add (line 721) | function add(string $name, array $array): void
  function get (line 737) | function get(string $name, $default = null)
  function has (line 749) | function has(string $name): bool
  function ask (line 758) | function ask(string $message, ?string $default = null, ?array $autocompl...
  function askChoice (line 793) | function askChoice(string $message, array $availableChoices, $default = ...
  function askConfirmation (line 832) | function askConfirmation(string $message, bool $default = false): bool
  function askHiddenResponse (line 860) | function askHiddenResponse(string $message): string
  function input (line 889) | function input(): InputInterface
  function output (line 894) | function output(): OutputInterface
  function commandExist (line 904) | function commandExist(string $command): bool
  function commandSupportsOption (line 912) | function commandSupportsOption(string $command, string $option): bool
  function which (line 924) | function which(string $name): string
  function remoteEnv (line 948) | function remoteEnv(): array
  function error (line 962) | function error(string $message): Exception
  function timestamp (line 970) | function timestamp(): string
  function fetch (line 982) | function fetch(string $url, string $method = 'get', array $headers = [],...

FILE: tests/joy/HostDefaultConfigTest.php
  class HostDefaultConfigTest (line 13) | class HostDefaultConfigTest extends JoyTest
    method recipe (line 15) | protected function recipe(): string
    method testOnFunc (line 29) | public function testOnFunc()

FILE: tests/joy/JoyTest.php
  class JoyTest (line 18) | abstract class JoyTest extends TestCase
    method setUpBeforeClass (line 30) | public static function setUpBeforeClass(): void
    method tearDownAfterClass (line 36) | public static function tearDownAfterClass(): void
    method cleanUp (line 41) | protected static function cleanUp()
    method init (line 48) | protected function init(string $recipe)
    method dep (line 60) | protected function dep(string $task, array $args = []): int
    method recipe (line 76) | abstract protected function recipe(): string;

FILE: tests/joy/OnFuncTest.php
  class OnFuncTest (line 11) | class OnFuncTest extends JoyTest
    method recipe (line 13) | protected function recipe(): string
    method testOnFunc (line 38) | public function testOnFunc()

FILE: tests/legacy/AbstractTest.php
  class AbstractTest (line 18) | abstract class AbstractTest extends TestCase
    method setUpBeforeClass (line 30) | public static function setUpBeforeClass(): void
    method tearDownAfterClass (line 36) | public static function tearDownAfterClass(): void
    method cleanUp (line 41) | protected static function cleanUp()
    method init (line 48) | protected function init(string $recipe)
    method dep (line 60) | protected function dep(string $recipe, string $task)

FILE: tests/legacy/CurrentPathTest.php
  class CurrentPathTest (line 12) | class CurrentPathTest extends AbstractTest
    method testDeployWithDifferentCurrentPath (line 16) | public function testDeployWithDifferentCurrentPath()

FILE: tests/legacy/DeployTest.php
  class DeployTest (line 12) | class DeployTest extends AbstractTest
    method testDeploy (line 16) | public function testDeploy()
    method testDeploySelectHosts (line 44) | public function testDeploySelectHosts()
    method testKeepReleases (line 55) | public function testKeepReleases()
    method testRollback (line 82) | public function testRollback()
    method testFail (line 95) | public function testFail()
    method testCleanup (line 112) | public function testCleanup()
    method testIsUnlockedExitsWithOneWhenDeployIsLocked (line 125) | public function testIsUnlockedExitsWithOneWhenDeployIsLocked()
    method testIsUnlockedExitsWithZeroWhenDeployIsNotLocked (line 135) | public function testIsUnlockedExitsWithZeroWhenDeployIsNotLocked()

FILE: tests/legacy/EnvTest.php
  class EnvTest (line 10) | class EnvTest extends AbstractTest
    method testOnce (line 14) | public function testOnce()

FILE: tests/legacy/OncePerNodeTest.php
  class OncePerNodeTest (line 10) | class OncePerNodeTest extends AbstractTest
    method testOnce (line 14) | public function testOnce()

FILE: tests/legacy/OnceTest.php
  class OnceTest (line 10) | class OnceTest extends AbstractTest
    method testOnce (line 14) | public function testOnce()

FILE: tests/legacy/ParallelTest.php
  class ParallelTest (line 12) | class ParallelTest extends AbstractTest
    method setUpBeforeClass (line 16) | public static function setUpBeforeClass(): void
    method tearDownAfterClass (line 22) | public static function tearDownAfterClass(): void
    method testWorker (line 28) | public function testWorker()
    method testServer (line 41) | public function testServer()
    method testOption (line 58) | public function testOption()
    method testCachedHostConfig (line 81) | public function testCachedHostConfig()
    method testHostConfigFromCallback (line 98) | public function testHostConfigFromCallback()

FILE: tests/legacy/SelectTest.php
  class SelectTest (line 12) | class SelectTest extends AbstractTest
    method testSelect (line 16) | public function testSelect()

FILE: tests/legacy/UpdateCodeTest.php
  class UpdateCodeTest (line 12) | class UpdateCodeTest extends AbstractTest
    method testDeployWithDifferentUpdateCodeTask (line 16) | public function testDeployWithDifferentUpdateCodeTask()

FILE: tests/legacy/YamlTest.php
  class YamlTest (line 12) | class YamlTest extends AbstractTest
    method testDeploy (line 16) | public function testDeploy()

FILE: tests/src/Collection/CollectionTest.php
  class CollectionTest (line 14) | class CollectionTest extends TestCase
    method collections (line 16) | public static function collections()
    method testCollection (line 28) | public function testCollection($collection)
    method testException (line 47) | public function testException($collection)

FILE: tests/src/Command/BlackjackCommandTest.php
  class BlackjackCommandTest (line 6) | class BlackjackCommandTest extends TestCase
    method testHandValue (line 8) | public function testHandValue()

FILE: tests/src/Component/Pimple/PimpleTest.php
  class PimpleTest (line 22) | class PimpleTest extends TestCase
    method testWithString (line 24) | public function testWithString()
    method testWithClosure (line 32) | public function testWithClosure()
    method testServicesShouldBeDifferent (line 42) | public function testServicesShouldBeDifferent()
    method testShouldPassContainerAsParameter (line 58) | public function testShouldPassContainerAsParameter()
    method testIsset (line 72) | public function testIsset()
    method testConstructorInjection (line 88) | public function testConstructorInjection()
    method testOffsetGetValidatesKeyIsPresent (line 96) | public function testOffsetGetValidatesKeyIsPresent()
    method testLegacyOffsetGetValidatesKeyIsPresent (line 108) | public function testLegacyOffsetGetValidatesKeyIsPresent()
    method testOffsetGetHonorsNullValues (line 117) | public function testOffsetGetHonorsNullValues()
    method testUnset (line 124) | public function testUnset()
    method testShare (line 140) | public function testShare($service)
    method testProtect (line 157) | public function testProtect($service)
    method testGlobalFunctionNameAsParameterValue (line 165) | public function testGlobalFunctionNameAsParameterValue()
    method testRaw (line 172) | public function testRaw()
    method testRawHonorsNullValues (line 181) | public function testRawHonorsNullValues()
    method testRawValidatesKeyIsPresent (line 188) | public function testRawValidatesKeyIsPresent()
    method testLegacyRawValidatesKeyIsPresent (line 200) | public function testLegacyRawValidatesKeyIsPresent()
    method testExtend (line 212) | public function testExtend($service)
    method testExtendDoesNotLeakWithFactories (line 239) | public function testExtendDoesNotLeakWithFactories()
    method testExtendValidatesKeyIsPresent (line 263) | public function testExtendValidatesKeyIsPresent()
    method testLegacyExtendValidatesKeyIsPresent (line 275) | public function testLegacyExtendValidatesKeyIsPresent()
    method testKeys (line 284) | public function testKeys()
    method settingAnInvokableObjectShouldTreatItAsFactory (line 294) | public function settingAnInvokableObjectShouldTreatItAsFactory()
    method settingNonInvokableObjectShouldTreatItAsParameter (line 303) | public function settingNonInvokableObjectShouldTreatItAsParameter()
    method testFactoryFailsForInvalidServiceDefinitions (line 314) | public function testFactoryFailsForInvalidServiceDefinitions($service)
    method testLegacyFactoryFailsForInvalidServiceDefinitions (line 325) | public function testLegacyFactoryFailsForInvalidServiceDefinitions($se...
    method testProtectFailsForInvalidServiceDefinitions (line 335) | public function testProtectFailsForInvalidServiceDefinitions($service)
    method testLegacyProtectFailsForInvalidServiceDefinitions (line 346) | public function testLegacyProtectFailsForInvalidServiceDefinitions($se...
    method testExtendFailsForKeysNotContainingServiceDefinitions (line 356) | public function testExtendFailsForKeysNotContainingServiceDefinitions(...
    method testLegacyExtendFailsForKeysNotContainingServiceDefinitions (line 370) | public function testLegacyExtendFailsForKeysNotContainingServiceDefini...
    method testExtendingProtectedClosureDeprecation (line 384) | public function testExtendingProtectedClosureDeprecation()
    method testExtendFailsForInvalidServiceDefinitions (line 401) | public function testExtendFailsForInvalidServiceDefinitions($service)
    method testLegacyExtendFailsForInvalidServiceDefinitions (line 413) | public function testLegacyExtendFailsForInvalidServiceDefinitions($ser...
    method testExtendFailsIfFrozenServiceIsNonInvokable (line 421) | public function testExtendFailsIfFrozenServiceIsNonInvokable()
    method testExtendFailsIfFrozenServiceIsInvokable (line 435) | public function testExtendFailsIfFrozenServiceIsInvokable()
    method badServiceDefinitionProvider (line 452) | public static function badServiceDefinitionProvider()
    method serviceDefinitionProvider (line 463) | public static function serviceDefinitionProvider()
    method testDefiningNewServiceAfterFreeze (line 476) | public function testDefiningNewServiceAfterFreeze()
    method testOverridingServiceAfterFreeze (line 490) | public function testOverridingServiceAfterFreeze()
    method testLegacyOverridingServiceAfterFreeze (line 509) | public function testLegacyOverridingServiceAfterFreeze()
    method testRemovingServiceAfterFreeze (line 525) | public function testRemovingServiceAfterFreeze()
    method testExtendingService (line 540) | public function testExtendingService()
    method testExtendingServiceAfterOtherServiceFreeze (line 555) | public function testExtendingServiceAfterOtherServiceFreeze()
  class Invokable (line 573) | class Invokable
    method __invoke (line 575) | public function __invoke($value = null)
  class NonInvokable (line 584) | class NonInvokable
    method __call (line 586) | public function __call($a, $b) {}
  class Service (line 589) | class Service

FILE: tests/src/Configuration/ConfigurationTest.php
  class ConfigurationTest (line 9) | class ConfigurationTest extends TestCase
    method testParse (line 11) | public function testParse()
    method testUnset (line 20) | public function testUnset()
    method testGet (line 28) | public function testGet()
    method testGetDefault (line 41) | public function testGetDefault()
    method testGetException (line 49) | public function testGetException()
    method testGetParent (line 59) | public function testGetParent()
    method testGetParentParent (line 78) | public function testGetParentParent()
    method testGetParentWhatDependsOnChild (line 90) | public function testGetParentWhatDependsOnChild()
    method testGetFromCallback (line 104) | public function testGetFromCallback()
    method testAdd (line 113) | public function testAdd()
    method testAddEmpty (line 121) | public function testAddEmpty()
    method testAddDefaultToNotArray (line 128) | public function testAddDefaultToNotArray()
    method testAddToParent (line 138) | public function testAddToParent()
    method testAddToParentCallback (line 149) | public function testAddToParentCallback()
    method testPersist (line 162) | public function testPersist()

FILE: tests/src/DeployerTest.php
  class DeployerTest (line 15) | class DeployerTest extends TestCase
    method setUp (line 19) | protected function setUp(): void
    method tearDown (line 27) | protected function tearDown(): void
    method testInstance (line 32) | public function testInstance()

FILE: tests/src/FunctionsTest.php
  class FunctionsTest (line 23) | class FunctionsTest extends TestCase
    method setUp (line 30) | protected function setUp(): void
    method tearDown (line 44) | protected function tearDown(): void
    method testHost (line 51) | public function testHost()
    method testLocalhost (line 65) | public function testLocalhost()
    method testTask (line 71) | public function testTask()
    method testBefore (line 86) | public function testBefore()
    method testAfter (line 97) | public function testAfter()
    method testRunLocally (line 108) | public function testRunLocally()
    method testWithinSetsWorkingPaths (line 114) | public function testWithinSetsWorkingPaths()
    method testWithinRestoresWorkingPathInCaseOfException (line 127) | public function testWithinRestoresWorkingPathInCaseOfException()
    method testWithinReturningValue (line 143) | public function testWithinReturningValue()
    method testWithinWithVoidFunction (line 152) | public function testWithinWithVoidFunction()
    method taskToNames (line 161) | private function taskToNames($tasks)

FILE: tests/src/Host/ConfigurationTest.php
  class ConfigurationTest (line 14) | class ConfigurationTest extends TestCase
    method testConfiguration (line 16) | public function testConfiguration()
    method testAddParams (line 44) | public function testAddParams()
    method testAddParamsToNotArray (line 79) | public function testAddParamsToNotArray()

FILE: tests/src/Host/HostTest.php
  class HostTest (line 13) | class HostTest extends TestCase
    method testHost (line 15) | public function testHost()
    method testConfigurationAccessor (line 38) | public function testConfigurationAccessor()
    method testHostAlias (line 52) | public function testHostAlias()
    method testHostWithParams (line 59) | public function testHostWithParams()
    method testHostWithUserFromConfig (line 70) | public function testHostWithUserFromConfig()

FILE: tests/src/Host/RangeTest.php
  class RangeTest (line 12) | class RangeTest extends TestCase
    method testExpand (line 14) | public function testExpand()

FILE: tests/src/Importer/ImporterTest.php
  class ImporterTest (line 10) | class ImporterTest extends TestCase
    method setUp (line 15) | public function setUp(): void
    method tearDown (line 22) | public function tearDown(): void
    method testCanOneOverrideStaticMethod (line 28) | public function testCanOneOverrideStaticMethod(): void
    method testImporterIgnoresYamlHiddenKeys (line 50) | public function testImporterIgnoresYamlHiddenKeys(): void

FILE: tests/src/Selector/SelectorTest.php
  class SelectorTest (line 9) | class SelectorTest extends TestCase
    method testSelectHosts (line 11) | public function testSelectHosts()

FILE: tests/src/Ssh/IOArgumentsTest.php
  class IOArgumentsTest (line 12) | class IOArgumentsTest extends TestCase
    method testCollect (line 14) | public function testCollect()

FILE: tests/src/Support/HelpersTest.php
  class HelpersTest (line 12) | class HelpersTest extends TestCase
    method testArrayFlatten (line 14) | public function testArrayFlatten()
    method testArrayMergeAlternate (line 19) | public function testArrayMergeAlternate()
    method testParseHomeDir (line 55) | public function testParseHomeDir()
    method testEscapeShellArgument (line 63) | public function testEscapeShellArgument()

FILE: tests/src/Support/ObjectProxyTest.php
  class ObjectProxyTest (line 12) | class ObjectProxyTest extends TestCase
    method testObjectProxy (line 14) | public function testObjectProxy()

FILE: tests/src/Task/ContextTest.php
  class ContextTest (line 16) | class ContextTest extends TestCase
    method testContext (line 18) | public function testContext()

FILE: tests/src/Task/ScriptManagerTest.php
  class ScriptManagerTest (line 12) | class ScriptManagerTest extends TestCase
    method testGetTasks (line 14) | public function testGetTasks()
    method testOnce (line 34) | public function testOnce()
    method testSelectsCombine (line 57) | public function testSelectsCombine()
    method testThrowsExceptionIfTaskCollectionEmpty (line 85) | public function testThrowsExceptionIfTaskCollectionEmpty()
    method testThrowsExceptionIfTaskDontExists (line 93) | public function testThrowsExceptionIfTaskDontExists()

FILE: tests/src/Task/TaskTest.php
  class TaskTest (line 16) | class TaskTest extends TestCase
    method tearDown (line 18) | protected function tearDown(): void
    method testTask (line 23) | public function testTask()
    method testInit (line 56) | public function testInit()
    method testGroupInvoke (line 88) | public function testGroupInvoke(): void
  class StubTask (line 107) | class StubTask
    method __invoke (line 111) | public function __invoke()
Condensed preview — 338 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,100K chars).
[
  {
    "path": ".gitattributes",
    "chars": 203,
    "preview": "/.gitattributes export-ignore\n/.github/ export-ignore\n/.gitignore export-ignore\n/docs/ export-ignore\n/phpcs.xml export-i"
  },
  {
    "path": ".github/DISCUSSION_TEMPLATE/bugs.yml",
    "chars": 1458,
    "preview": "body:\n  - type: markdown\n    attributes:\n      value: |\n        **Before opening a bug report, please search the existin"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 18,
    "preview": "github: antonmedv\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 653,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Bug Report\n    url: https://github.com/deployphp/deployer/discussio"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 170,
    "preview": "- [ ] Bug fix #…?\n- [ ] New feature?\n- [ ] BC breaks?\n- [ ] Tests added?\n- [ ] Docs added?\n\n      Please, regenerate doc"
  },
  {
    "path": ".github/labeler.yml",
    "chars": 24,
    "preview": "v7:\n- base-branch: \"7.x\""
  },
  {
    "path": ".github/workflows/check.yml",
    "chars": 1898,
    "preview": "name: check\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  phpstan:\n    runs-on"
  },
  {
    "path": ".github/workflows/docker.yml",
    "chars": 1802,
    "preview": "name: docker\n\non:\n  release:\n    types: [ published ]\n  workflow_dispatch:\n    inputs:\n      version:\n        descriptio"
  },
  {
    "path": ".github/workflows/docs-sync.yml",
    "chars": 1112,
    "preview": "name: doc-sync\n\non:\n  push:\n    branches: [ master ]\n\npermissions:\n  contents: write\n\njobs:\n  docgen-and-commit:\n    run"
  },
  {
    "path": ".github/workflows/docs.yml",
    "chars": 1166,
    "preview": "name: doc\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  docgen:\n    runs-on: u"
  },
  {
    "path": ".github/workflows/labeler.yml",
    "chars": 195,
    "preview": "name: labeler\n\non:\n- pull_request_target\n\njobs:\n  labeler:\n    permissions:\n      contents: read\n      pull-requests: wr"
  },
  {
    "path": ".github/workflows/lint.yml",
    "chars": 584,
    "preview": "name: lint\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    types: [opened, synchronize, reopened, ready_for_re"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 1484,
    "preview": "name: release\n\non:\n  release:\n    types: [published]\n  workflow_dispatch:\n    inputs:\n      version:\n        description"
  },
  {
    "path": ".github/workflows/stale.yml",
    "chars": 668,
    "preview": "name: stale\non:\n  schedule:\n    - cron: \"* * * * *\"\n  workflow_dispatch:\n\njobs:\n  close-issues:\n    runs-on: ubuntu-late"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 1141,
    "preview": "name: test\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  unit:\n    runs-on: ub"
  },
  {
    "path": ".gitignore",
    "chars": 93,
    "preview": "/vendor/\n*.phar\n.phpunit.result.cache\ndocker-compose.override.yml\n.php-cs-fixer.cache\n.idea/\n"
  },
  {
    "path": ".php-cs-fixer.dist.php",
    "chars": 607,
    "preview": "<?php\n\n$finder = (new PhpCsFixer\\Finder())\n    ->in(__DIR__ . '/src')\n    ->in(__DIR__ . '/recipe')\n    ->in(__DIR__ . '"
  },
  {
    "path": "Dockerfile",
    "chars": 248,
    "preview": "FROM php:8.4-cli-alpine\n\nRUN apk add --no-cache bash git openssh-client rsync zip unzip libzip-dev \\\n\nRUN docker-php-ext"
  },
  {
    "path": "LICENSE",
    "chars": 1079,
    "preview": "The MIT License (MIT)\n\nCopyright © 2013 Anton Medvedev\n\nPermission is hereby granted, free of charge, to any person obta"
  },
  {
    "path": "README.md",
    "chars": 2124,
    "preview": "<h1>\n    <a href=\"https://deployer.org\">\n        <picture>\n            <source media=\"(prefers-color-scheme: dark)\" srcs"
  },
  {
    "path": "SECURITY.md",
    "chars": 835,
    "preview": "# Security Policy\n\n## Supported Versions\n\nDeployer is generally backwards compatible with very few exceptions, so we\nrec"
  },
  {
    "path": "bin/build",
    "chars": 3089,
    "preview": "#!/usr/bin/env php\n<?php\n/* (c) Anton Medvedev <anton@medv.io>\n *\n * For the full copyright and license information, ple"
  },
  {
    "path": "bin/dep",
    "chars": 2927,
    "preview": "#!/usr/bin/env php\n<?php\n/* (c) Anton Medvedev <anton@medv.io>\n *\n * For the full copyright and license information, ple"
  },
  {
    "path": "bin/docgen",
    "chars": 1612,
    "preview": "#!/usr/bin/env php\n<?php\n/* (c) Anton Medvedev <anton@medv.io>\n *\n * For the full copyright and license information, ple"
  },
  {
    "path": "composer.json",
    "chars": 1751,
    "preview": "{\n    \"name\": \"deployer/deployer\",\n    \"description\": \"Deployment Tool\",\n    \"license\": \"MIT\",\n    \"homepage\": \"https://"
  },
  {
    "path": "contrib/bugsnag.php",
    "chars": 1298,
    "preview": "<?php\n/*\n\n## Configuration\n\n- *bugsnag_api_key* – the API Key associated with the project. Informs Bugsnag which project"
  },
  {
    "path": "contrib/cachetool.php",
    "chars": 3709,
    "preview": "<?php\n/*\n\n## Configuration\n\n- **cachetool** *(optional)*: accepts a *string* or an *array* of strings with the unix sock"
  },
  {
    "path": "contrib/chatwork.php",
    "chars": 4959,
    "preview": "<?php\n/*\n# Chatwork Recipe\n\n## Installing\n  1. Create chatwork account by any manual in the internet\n  2. Take chatwork "
  },
  {
    "path": "contrib/cimonitor.php",
    "chars": 4601,
    "preview": "<?php\n/*\nMonitor your deployments on [CIMonitor](https://github.com/CIMonitor/CIMonitor).\n\n![CIMonitorGif](https://www.s"
  },
  {
    "path": "contrib/cloudflare.php",
    "chars": 3063,
    "preview": "<?php\n/*\n\n### Configuration\n\n- `cloudflare` – array with configuration for cloudflare\n    - `service_key` – Cloudflare S"
  },
  {
    "path": "contrib/cpanel.php",
    "chars": 10043,
    "preview": "<?php\n/*\n### Description\nThis is a recipe that uses the [cPanel 2 API](https://documentation.cPanel.net/display/DD/Guide"
  },
  {
    "path": "contrib/crontab.php",
    "chars": 4575,
    "preview": "<?php\n/*\nRecipe for adding crontab jobs.\n\nThis recipe creates a new section in the crontab file with the configured jobs"
  },
  {
    "path": "contrib/directadmin.php",
    "chars": 6258,
    "preview": "<?php\n/*\n### Configuration\n- `directadmin` – array with configuration for DirectAdmin\n    - `host` – DirectAdmin host\n  "
  },
  {
    "path": "contrib/discord.php",
    "chars": 3044,
    "preview": "<?php\n/*\n## Installing\n\nAdd hook on deploy:\n\n```php\nbefore('deploy', 'discord:notify');\n```\n\n## Configuration\n\n- `discor"
  },
  {
    "path": "contrib/grafana.php",
    "chars": 1868,
    "preview": "<?php\n/*\n\n## Configuration options\n\n- **url** *(required)*: the URL to the creates annotation api endpoint.\n- **token** "
  },
  {
    "path": "contrib/hangouts.php",
    "chars": 5454,
    "preview": "<?php\n/*\n\nAdd hook on deploy:\n\n```php\nbefore('deploy', 'chat:notify');\n```\n\n## Configuration\n\n- `chat_webhook` – chat in"
  },
  {
    "path": "contrib/hipchat.php",
    "chars": 1308,
    "preview": "<?php\n/*\n## Configuration\n\n- `hipchat_token` – Hipchat V1 auth token\n- `hipchat_room_id` – Room ID or name\n- `hipchat_me"
  },
  {
    "path": "contrib/ispmanager.php",
    "chars": 23772,
    "preview": "<?php\n/*\n * This recipe for work with ISPManager Lite panel by API.\n */\n\nnamespace Deployer;\n\nuse Deployer\\Exception\\Exc"
  },
  {
    "path": "contrib/mattermost.php",
    "chars": 4134,
    "preview": "<?php\n/*\n## Installing\n\nCreate a Mattermost incoming webhook, through the administration panel.\n\nAdd hook on deploy:\n\n``"
  },
  {
    "path": "contrib/ms-teams.php",
    "chars": 4322,
    "preview": "<?php\n/*\n## Installing\n\nRequire ms-teams recipe in your `deploy.php` file:\n\nSetup:\n1. Open MS Teams\n2. Navigate to Teams"
  },
  {
    "path": "contrib/newrelic.php",
    "chars": 1446,
    "preview": "<?php\n/*\n## Configuration\n\n- `newrelic_app_id` – newrelic's app id\n- `newrelic_api_key` – newrelic's api key\n- `newrelic"
  },
  {
    "path": "contrib/npm.php",
    "chars": 669,
    "preview": "<?php\n/*\n## Configuration\n\n- `bin/npm` *(optional)*: set npm binary, automatically detected otherwise.\n\n## Usage\n\n```php"
  },
  {
    "path": "contrib/ntfy.php",
    "chars": 4085,
    "preview": "<?php\n/*\n## Installing\n\nRequire ntfy.sh recipe in your `deploy.php` file:\n\nSetup:\n1. Setup deploy.php\n    Add in header:"
  },
  {
    "path": "contrib/phinx.php",
    "chars": 5092,
    "preview": "<?php\n/*\n\n## Configuration options\n\nAll options are in the config parameter `phinx` specified as an array (instead of th"
  },
  {
    "path": "contrib/php-fpm.php",
    "chars": 1478,
    "preview": "<?php\n/*\n\n:::caution\nDo **not** reload php-fpm. Some user requests could fail or not complete in the\nprocess of reloadin"
  },
  {
    "path": "contrib/rabbit.php",
    "chars": 3079,
    "preview": "<?php\n/*\n### Installing\n\n```php\n// deploy.php\n\nrequire 'recipe/rabbit.php';\n```\n\n### Configuration options\n\n- **rabbit**"
  },
  {
    "path": "contrib/raygun.php",
    "chars": 1238,
    "preview": "<?php\n/*\n\n## Configuration\n\n- `raygun_api_key` – the API key of your Raygun application\n- `raygun_version` – the version"
  },
  {
    "path": "contrib/rocketchat.php",
    "chars": 4731,
    "preview": "<?php\n/*\n## Installing\n\nCreate a RocketChat incoming webhook, through the administration panel.\n\nAdd hook on deploy:\n\n``"
  },
  {
    "path": "contrib/rollbar.php",
    "chars": 1166,
    "preview": "<?php\n/*\n\n## Configuration\n\n- `rollbar_token` – access token to rollbar api\n- `rollbar_comment` – comment about deploy, "
  },
  {
    "path": "contrib/rsync.php",
    "chars": 8406,
    "preview": "<?php\n/*\n:::warning\nThis must not be confused with `/src/Utility/Rsync.php`, deployer's built-in rsync. Their configurat"
  },
  {
    "path": "contrib/sentry.php",
    "chars": 10979,
    "preview": "<?php\n/*\n\n### Configuration options\n\n- **organization** *(required)*: the slug of the organization the release belongs t"
  },
  {
    "path": "contrib/slack.php",
    "chars": 5089,
    "preview": "<?php\n/*\n## Installing\n\n<a href=\"https://slack.com/oauth/authorize?&client_id=113734341365.225973502034&scope=incoming-w"
  },
  {
    "path": "contrib/supervisord-monitor.php",
    "chars": 5361,
    "preview": "<?php\n/*\n### Description\nThis is a recipe that uses the [Supervisord server monitoring project](https://github.com/mlaza"
  },
  {
    "path": "contrib/telegram.php",
    "chars": 4467,
    "preview": "<?php\n/*\n## Installing\n  1. Create telegram bot with [BotFather](https://t.me/BotFather) and grab the token provided\n  2"
  },
  {
    "path": "contrib/webpack_encore.php",
    "chars": 1037,
    "preview": "<?php\n/*\n\n## Configuration\n\n- **webpack_encore/package_manager** *(optional)*: set yarn or npm. We try to find if yarn o"
  },
  {
    "path": "contrib/workplace.php",
    "chars": 3499,
    "preview": "<?php\n/*\nThis recipes works with Custom Integrations and Publishing Bots.\n\n\nAdd hook on deploy:\n\n```\nbefore('deploy', 'w"
  },
  {
    "path": "contrib/yammer.php",
    "chars": 3242,
    "preview": "<?php\n/*\n\nAdd hook on deploy:\n\n```php\nbefore('deploy', 'yammer:notify');\n```\n\n## Configuration\n\n- `yammer_url` – The URL"
  },
  {
    "path": "contrib/yarn.php",
    "chars": 681,
    "preview": "<?php\n/*\n## Configuration\n\n- **bin/yarn** *(optional)*: set Yarn binary, automatically detected otherwise.\n\n## Usage\n\n``"
  },
  {
    "path": "docs/KNOWN_BUGS.md",
    "chars": 1514,
    "preview": "# Known Bugs\n\n## Ubuntu 14.04, Coreutils 8.21\n\nThere are known bugs with relative symlinks `ln --relative`, which may ca"
  },
  {
    "path": "docs/UPGRADE.md",
    "chars": 8073,
    "preview": "# Upgrade a major version\n\n## Upgrade from 7.x to 8.x\n\n- `run()` and `runLocally()` doesn't accept `options` parameter a"
  },
  {
    "path": "docs/api.md",
    "chars": 11014,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit src/functions.php -->\n<!-- Then run bin/docgen -->\n\n# API Reference\n\n#"
  },
  {
    "path": "docs/avoid-php-fpm-reloading.md",
    "chars": 1565,
    "preview": "# Avoid PHP-FPM Reloading\n\nDeployer symlinks _current_ to latest release dir.\n\n```\ncurrent -> releases/3/\nreleases/\n    "
  },
  {
    "path": "docs/basics.md",
    "chars": 5986,
    "preview": "# Basics\n\nDeployer operates around two main concepts: [**hosts**](hosts.md) and [**tasks**](tasks.md). These are defined"
  },
  {
    "path": "docs/ci-cd.md",
    "chars": 4637,
    "preview": "# CI/CD\n\n## GitHub Actions\n\nUse official [GitHub Action for Deployer](https://github.com/deployphp/action).\n\nCreate `.gi"
  },
  {
    "path": "docs/cli.md",
    "chars": 5054,
    "preview": "# CLI Usage\n\nWe recommend adding the following alias to your .bashrc file:\n\n```bash\nalias dep='vendor/bin/dep'\n```\n\nIt i"
  },
  {
    "path": "docs/contrib/README.md",
    "chars": 1557,
    "preview": "# All Contrib Recipes\n\n* [Bugsnag Recipe](/docs/contrib/bugsnag.md)\n* [Cachetool Recipe](/docs/contrib/cachetool.md)\n* ["
  },
  {
    "path": "docs/contrib/bugsnag.md",
    "chars": 1041,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/bugsnag.php -->\n<!-- Then run bin/docgen -->\n\n# Bugsnag Recipe"
  },
  {
    "path": "docs/contrib/cachetool.md",
    "chars": 5332,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/cachetool.php -->\n<!-- Then run bin/docgen -->\n\n# Cachetool Re"
  },
  {
    "path": "docs/contrib/chatwork.md",
    "chars": 4825,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/chatwork.php -->\n<!-- Then run bin/docgen -->\n\n# Chatwork Reci"
  },
  {
    "path": "docs/contrib/cimonitor.md",
    "chars": 4558,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/cimonitor.php -->\n<!-- Then run bin/docgen -->\n\n# Cimonitor Re"
  },
  {
    "path": "docs/contrib/cloudflare.md",
    "chars": 1754,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/cloudflare.php -->\n<!-- Then run bin/docgen -->\n\n# Cloudflare "
  },
  {
    "path": "docs/contrib/cpanel.md",
    "chars": 5527,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/cpanel.php -->\n<!-- Then run bin/docgen -->\n\n# Cpanel Recipe\n\n"
  },
  {
    "path": "docs/contrib/crontab.md",
    "chars": 1723,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/crontab.php -->\n<!-- Then run bin/docgen -->\n\n# Crontab Recipe"
  },
  {
    "path": "docs/contrib/directadmin.md",
    "chars": 2581,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/directadmin.php -->\n<!-- Then run bin/docgen -->\n\n# Directadmi"
  },
  {
    "path": "docs/contrib/discord.md",
    "chars": 3335,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/discord.php -->\n<!-- Then run bin/docgen -->\n\n# Discord Recipe"
  },
  {
    "path": "docs/contrib/grafana.md",
    "chars": 1170,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/grafana.php -->\n<!-- Then run bin/docgen -->\n\n# Grafana Recipe"
  },
  {
    "path": "docs/contrib/hangouts.md",
    "chars": 2601,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/hangouts.php -->\n<!-- Then run bin/docgen -->\n\n# Hangouts Reci"
  },
  {
    "path": "docs/contrib/hipchat.md",
    "chars": 1648,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/hipchat.php -->\n<!-- Then run bin/docgen -->\n\n# Hipchat Recipe"
  },
  {
    "path": "docs/contrib/ispmanager.md",
    "chars": 4915,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/ispmanager.php -->\n<!-- Then run bin/docgen -->\n\n# Ispmanager "
  },
  {
    "path": "docs/contrib/mattermost.md",
    "chars": 4281,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/mattermost.php -->\n<!-- Then run bin/docgen -->\n\n# Mattermost "
  },
  {
    "path": "docs/contrib/ms-teams.md",
    "chars": 3987,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/ms-teams.php -->\n<!-- Then run bin/docgen -->\n\n# Ms-teams Reci"
  },
  {
    "path": "docs/contrib/newrelic.md",
    "chars": 1516,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/newrelic.php -->\n<!-- Then run bin/docgen -->\n\n# Newrelic Reci"
  },
  {
    "path": "docs/contrib/npm.md",
    "chars": 1096,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/npm.php -->\n<!-- Then run bin/docgen -->\n\n# Npm Recipe\n\n```php"
  },
  {
    "path": "docs/contrib/ntfy.md",
    "chars": 3927,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/ntfy.php -->\n<!-- Then run bin/docgen -->\n\n# Ntfy Recipe\n\n```p"
  },
  {
    "path": "docs/contrib/phinx.md",
    "chars": 2955,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/phinx.php -->\n<!-- Then run bin/docgen -->\n\n# Phinx Recipe\n\n``"
  },
  {
    "path": "docs/contrib/php-fpm.md",
    "chars": 2894,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/php-fpm.php -->\n<!-- Then run bin/docgen -->\n\n# Php-fpm Recipe"
  },
  {
    "path": "docs/contrib/rabbit.md",
    "chars": 1409,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/rabbit.php -->\n<!-- Then run bin/docgen -->\n\n# Rabbit Recipe\n\n"
  },
  {
    "path": "docs/contrib/raygun.md",
    "chars": 995,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/raygun.php -->\n<!-- Then run bin/docgen -->\n\n# Raygun Recipe\n\n"
  },
  {
    "path": "docs/contrib/rocketchat.md",
    "chars": 4327,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/rocketchat.php -->\n<!-- Then run bin/docgen -->\n\n# Rocketchat "
  },
  {
    "path": "docs/contrib/rollbar.md",
    "chars": 1022,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/rollbar.php -->\n<!-- Then run bin/docgen -->\n\n# Rollbar Recipe"
  },
  {
    "path": "docs/contrib/rsync.md",
    "chars": 6107,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/rsync.php -->\n<!-- Then run bin/docgen -->\n\n# Rsync Recipe\n\n``"
  },
  {
    "path": "docs/contrib/sentry.md",
    "chars": 3134,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/sentry.php -->\n<!-- Then run bin/docgen -->\n\n# Sentry Recipe\n\n"
  },
  {
    "path": "docs/contrib/slack.md",
    "chars": 4557,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/slack.php -->\n<!-- Then run bin/docgen -->\n\n# Slack Recipe\n\n``"
  },
  {
    "path": "docs/contrib/supervisord-monitor.md",
    "chars": 3044,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/supervisord-monitor.php -->\n<!-- Then run bin/docgen -->\n\n# Su"
  },
  {
    "path": "docs/contrib/telegram.md",
    "chars": 3474,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/telegram.php -->\n<!-- Then run bin/docgen -->\n\n# Telegram Reci"
  },
  {
    "path": "docs/contrib/webpack_encore.md",
    "chars": 1523,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/webpack_encore.php -->\n<!-- Then run bin/docgen -->\n\n# Webpack"
  },
  {
    "path": "docs/contrib/workplace.md",
    "chars": 3171,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/workplace.php -->\n<!-- Then run bin/docgen -->\n\n# Workplace Re"
  },
  {
    "path": "docs/contrib/yammer.md",
    "chars": 2806,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/yammer.php -->\n<!-- Then run bin/docgen -->\n\n# Yammer Recipe\n\n"
  },
  {
    "path": "docs/contrib/yarn.md",
    "chars": 943,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit contrib/yarn.php -->\n<!-- Then run bin/docgen -->\n\n# Yarn Recipe\n\n```p"
  },
  {
    "path": "docs/getting-started.md",
    "chars": 5226,
    "preview": "# Getting Started\n\nThis tutorial will guide you through:\n\n- Setting up a new host with the [provision](recipe/provision."
  },
  {
    "path": "docs/hosts.md",
    "chars": 6211,
    "preview": "# Hosts\n\nIn Deployer, you define hosts using the [host()](api.md#host) function.\n\n### Defining a Host\n\n```php\nhost('exam"
  },
  {
    "path": "docs/installation.md",
    "chars": 3011,
    "preview": "# Installation\n\nThere are two ways to install Deployer: globally or locally. Global installation is recommended for most"
  },
  {
    "path": "docs/recipe/README.md",
    "chars": 1305,
    "preview": "# All Recipes\n\n* [Cakephp Recipe](/docs/recipe/cakephp.md)\n* [Codeigniter 4 Recipe](/docs/recipe/codeigniter4.md)\n* [Cod"
  },
  {
    "path": "docs/recipe/cakephp.md",
    "chars": 4182,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/cakephp.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy a"
  },
  {
    "path": "docs/recipe/codeigniter.md",
    "chars": 3634,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/codeigniter.php -->\n<!-- Then run bin/docgen -->\n\n# How to Depl"
  },
  {
    "path": "docs/recipe/codeigniter4.md",
    "chars": 9079,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/codeigniter4.php -->\n<!-- Then run bin/docgen -->\n\n# How to Dep"
  },
  {
    "path": "docs/recipe/common.md",
    "chars": 6357,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/common.php -->\n<!-- Then run bin/docgen -->\n\n# Common Recipe\n\n`"
  },
  {
    "path": "docs/recipe/composer.md",
    "chars": 625,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/composer.php -->\n<!-- Then run bin/docgen -->\n\n# Composer Recip"
  },
  {
    "path": "docs/recipe/contao.md",
    "chars": 6675,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/contao.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy a "
  },
  {
    "path": "docs/recipe/craftcms.md",
    "chars": 3666,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/craftcms.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy "
  },
  {
    "path": "docs/recipe/deploy/check_remote.md",
    "chars": 558,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/check_remote.php -->\n<!-- Then run bin/docgen -->\n\n# Che"
  },
  {
    "path": "docs/recipe/deploy/cleanup.md",
    "chars": 599,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/cleanup.php -->\n<!-- Then run bin/docgen -->\n\n# Cleanup "
  },
  {
    "path": "docs/recipe/deploy/clear_paths.md",
    "chars": 834,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/clear_paths.php -->\n<!-- Then run bin/docgen -->\n\n# Clea"
  },
  {
    "path": "docs/recipe/deploy/copy_dirs.md",
    "chars": 623,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/copy_dirs.php -->\n<!-- Then run bin/docgen -->\n\n# Copy D"
  },
  {
    "path": "docs/recipe/deploy/env.md",
    "chars": 526,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/env.php -->\n<!-- Then run bin/docgen -->\n\n# Env Recipe\n\n"
  },
  {
    "path": "docs/recipe/deploy/info.md",
    "chars": 1006,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/info.php -->\n<!-- Then run bin/docgen -->\n\n# Info Recipe"
  },
  {
    "path": "docs/recipe/deploy/lock.md",
    "chars": 658,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/lock.php -->\n<!-- Then run bin/docgen -->\n\n# Lock Recipe"
  },
  {
    "path": "docs/recipe/deploy/push.md",
    "chars": 523,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/push.php -->\n<!-- Then run bin/docgen -->\n\n# Push Recipe"
  },
  {
    "path": "docs/recipe/deploy/release.md",
    "chars": 2839,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/release.php -->\n<!-- Then run bin/docgen -->\n\n# Release "
  },
  {
    "path": "docs/recipe/deploy/rollback.md",
    "chars": 1306,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/rollback.php -->\n<!-- Then run bin/docgen -->\n\n# Rollbac"
  },
  {
    "path": "docs/recipe/deploy/setup.md",
    "chars": 371,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/setup.php -->\n<!-- Then run bin/docgen -->\n\n# Setup Reci"
  },
  {
    "path": "docs/recipe/deploy/shared.md",
    "chars": 1057,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/shared.php -->\n<!-- Then run bin/docgen -->\n\n# Shared Re"
  },
  {
    "path": "docs/recipe/deploy/symlink.md",
    "chars": 665,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/symlink.php -->\n<!-- Then run bin/docgen -->\n\n# Symlink "
  },
  {
    "path": "docs/recipe/deploy/update_code.md",
    "chars": 2543,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/update_code.php -->\n<!-- Then run bin/docgen -->\n\n# Upda"
  },
  {
    "path": "docs/recipe/deploy/vendors.md",
    "chars": 1240,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/vendors.php -->\n<!-- Then run bin/docgen -->\n\n# Vendors "
  },
  {
    "path": "docs/recipe/deploy/writable.md",
    "chars": 2617,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/deploy/writable.php -->\n<!-- Then run bin/docgen -->\n\n# Writabl"
  },
  {
    "path": "docs/recipe/drupal7.md",
    "chars": 4268,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/drupal7.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy a"
  },
  {
    "path": "docs/recipe/drupal8.md",
    "chars": 3949,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/drupal8.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy a"
  },
  {
    "path": "docs/recipe/flow_framework.md",
    "chars": 4538,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/flow_framework.php -->\n<!-- Then run bin/docgen -->\n\n# How to D"
  },
  {
    "path": "docs/recipe/fuelphp.md",
    "chars": 3280,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/fuelphp.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy a"
  },
  {
    "path": "docs/recipe/joomla.md",
    "chars": 3575,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/joomla.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy a "
  },
  {
    "path": "docs/recipe/laravel.md",
    "chars": 15224,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/laravel.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy a"
  },
  {
    "path": "docs/recipe/magento.md",
    "chars": 4189,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/magento.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy a"
  },
  {
    "path": "docs/recipe/magento2.md",
    "chars": 25994,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/magento2.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy "
  },
  {
    "path": "docs/recipe/pimcore.md",
    "chars": 3315,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/pimcore.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy a"
  },
  {
    "path": "docs/recipe/prestashop.md",
    "chars": 3775,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/prestashop.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deplo"
  },
  {
    "path": "docs/recipe/provision/databases.md",
    "chars": 1675,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/provision/databases.php -->\n<!-- Then run bin/docgen -->\n\n# Dat"
  },
  {
    "path": "docs/recipe/provision/nodejs.md",
    "chars": 561,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/provision/nodejs.php -->\n<!-- Then run bin/docgen -->\n\n# Nodejs"
  },
  {
    "path": "docs/recipe/provision/php.md",
    "chars": 893,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/provision/php.php -->\n<!-- Then run bin/docgen -->\n\n# Php Recip"
  },
  {
    "path": "docs/recipe/provision/user.md",
    "chars": 774,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/provision/user.php -->\n<!-- Then run bin/docgen -->\n\n# User Rec"
  },
  {
    "path": "docs/recipe/provision/website.md",
    "chars": 1242,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/provision/website.php -->\n<!-- Then run bin/docgen -->\n\n# Websi"
  },
  {
    "path": "docs/recipe/provision.md",
    "chars": 3442,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/provision.php -->\n<!-- Then run bin/docgen -->\n\n# Provision Rec"
  },
  {
    "path": "docs/recipe/shopware.md",
    "chars": 10212,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/shopware.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy "
  },
  {
    "path": "docs/recipe/silverstripe.md",
    "chars": 4705,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/silverstripe.php -->\n<!-- Then run bin/docgen -->\n\n# How to Dep"
  },
  {
    "path": "docs/recipe/spiral.md",
    "chars": 8192,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/spiral.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy a "
  },
  {
    "path": "docs/recipe/statamic.md",
    "chars": 7240,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/statamic.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy "
  },
  {
    "path": "docs/recipe/sulu.md",
    "chars": 3364,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/sulu.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy a Su"
  },
  {
    "path": "docs/recipe/symfony.md",
    "chars": 5600,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/symfony.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy a"
  },
  {
    "path": "docs/recipe/typo3.md",
    "chars": 10005,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/typo3.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy a T"
  },
  {
    "path": "docs/recipe/wordpress.md",
    "chars": 3628,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/wordpress.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy"
  },
  {
    "path": "docs/recipe/yii.md",
    "chars": 3792,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/yii.php -->\n<!-- Then run bin/docgen -->\n\n# How to Deploy a Yii"
  },
  {
    "path": "docs/recipe/zend_framework.md",
    "chars": 3015,
    "preview": "<!-- DO NOT EDIT THIS FILE! -->\n<!-- Instead edit recipe/zend_framework.php -->\n<!-- Then run bin/docgen -->\n\n# How to D"
  },
  {
    "path": "docs/selector.md",
    "chars": 4589,
    "preview": "# Selector\n\nDeployer uses the selector to choose hosts. Each host can have a set of labels.\nLabels are key-value pairs.\n"
  },
  {
    "path": "docs/sidebar.js",
    "chars": 330,
    "preview": "module.exports = [\n  \"installation\",\n  \"getting-started\",\n  \"basics\",\n  {\n    type: \"category\",\n    label: \"Main Concept"
  },
  {
    "path": "docs/tasks.md",
    "chars": 2388,
    "preview": "# Tasks\n\nDefine a task by using the [task](api.md#task) function. Also, you can give a description\nfor a task with the ["
  },
  {
    "path": "docs/yaml.md",
    "chars": 769,
    "preview": "# YAML\n\nDeployer supports recipes written in YAML. For validating the structure, Deployer uses\nthe JSON Schema declared "
  },
  {
    "path": "phpstan.neon",
    "chars": 415,
    "preview": "includes:\n    - tests/phpstan-baseline.neon\n\nparameters:\n    level: 5\n    paths:\n        - src\n        - recipe\n        "
  },
  {
    "path": "phpunit.xml",
    "chars": 935,
    "preview": "<?xml version=\"1.0\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" bootstrap=\"tests/bootstrap.php\" col"
  },
  {
    "path": "recipe/cakephp.php",
    "chars": 1000,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/common.php';\n\nadd('recipes', ['cakephp']);\n\n/**\n * CakePHP 4 Projec"
  },
  {
    "path": "recipe/codeigniter.php",
    "chars": 425,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/common.php';\n\nadd('recipes', ['codeigniter']);\n\n// CodeIgniter shar"
  },
  {
    "path": "recipe/codeigniter4.php",
    "chars": 5785,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/common.php';\n\nadd('recipes', ['codeigniter4']);\n\n// Default Configu"
  },
  {
    "path": "recipe/common.php",
    "chars": 4747,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire __DIR__ . '/provision.php';\nrequire __DIR__ . '/deploy/check_remote.php';\nrequire __"
  },
  {
    "path": "recipe/composer.php",
    "chars": 215,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/common.php';\n\nadd('recipes', ['composer']);\n\ndesc('Deploys your pro"
  },
  {
    "path": "recipe/contao.php",
    "chars": 3853,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/symfony.php';\n\nadd('recipes', ['contao']);\n\n// The public path is t"
  },
  {
    "path": "recipe/craftcms.php",
    "chars": 3451,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/common.php';\n\nadd('recipes', ['craftcms']);\n\nset('log_files', 'stor"
  },
  {
    "path": "recipe/deploy/check_remote.php",
    "chars": 1714,
    "preview": "<?php\n\nnamespace Deployer;\n\nuse Deployer\\Exception\\Exception;\nuse Deployer\\Exception\\GracefulShutdownException;\n\n// Canc"
  },
  {
    "path": "recipe/deploy/cleanup.php",
    "chars": 547,
    "preview": "<?php\n\nnamespace Deployer;\n\n// Use sudo in deploy:cleanup task for rm command.\nset('cleanup_use_sudo', false);\n\ndesc('Cl"
  },
  {
    "path": "recipe/deploy/clear_paths.php",
    "chars": 603,
    "preview": "<?php\n\nnamespace Deployer;\n\n// List of paths to remove from {{release_path}}.\nset('clear_paths', []);\n\n// Use sudo for d"
  },
  {
    "path": "recipe/deploy/copy_dirs.php",
    "chars": 1219,
    "preview": "<?php\n\nnamespace Deployer;\n\n// List of dirs to copy between releases.\n// For example you can copy `node_modules` to spee"
  },
  {
    "path": "recipe/deploy/env.php",
    "chars": 284,
    "preview": "<?php\n\nnamespace Deployer;\n\nset('dotenv_example', '.env.example');\n\ndesc('Configure .env file');\ntask('deploy:env', func"
  },
  {
    "path": "recipe/deploy/info.php",
    "chars": 1067,
    "preview": "<?php\n\nnamespace Deployer;\n\n// Defines \"what\" text for the 'deploy:info' task.\n// Uses one of the following sources:\n// "
  },
  {
    "path": "recipe/deploy/lock.php",
    "chars": 1055,
    "preview": "<?php\n\nnamespace Deployer;\n\nuse Deployer\\Exception\\GracefulShutdownException;\n\ndesc('Locks deploy');\ntask('deploy:lock',"
  },
  {
    "path": "recipe/deploy/push.php",
    "chars": 663,
    "preview": "<?php\n\nnamespace Deployer;\n\n// Creates patch of local changes and pushes them on host.\n// And applies to current_path. P"
  },
  {
    "path": "recipe/deploy/release.php",
    "chars": 6915,
    "preview": "<?php\n\nnamespace Deployer;\n\nuse Deployer\\Exception\\Exception;\nuse Symfony\\Component\\Console\\Helper\\Table;\n\nuse function "
  },
  {
    "path": "recipe/deploy/rollback.php",
    "chars": 2700,
    "preview": "<?php\n\nnamespace Deployer;\n\nuse Deployer\\Exception\\Exception;\n\n/*\n * Rollback candidate will be automatically chosen by "
  },
  {
    "path": "recipe/deploy/setup.php",
    "chars": 737,
    "preview": "<?php\n\nnamespace Deployer;\n\ndesc('Prepares host for deploy');\ntask('deploy:setup', function () {\n    run(\n        <<<EOF"
  },
  {
    "path": "recipe/deploy/shared.php",
    "chars": 3195,
    "preview": "<?php\n\nnamespace Deployer;\n\nuse Deployer\\Exception\\Exception;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\n// "
  },
  {
    "path": "recipe/deploy/symlink.php",
    "chars": 668,
    "preview": "<?php\n\nnamespace Deployer;\n\n// Use mv -T if available. Will check automatically.\nset('use_atomic_symlink', function () {"
  },
  {
    "path": "recipe/deploy/update_code.php",
    "chars": 4833,
    "preview": "<?php\n\nnamespace Deployer;\n\nuse Deployer\\Exception\\ConfigurationException;\nuse Symfony\\Component\\Console\\Input\\InputOpti"
  },
  {
    "path": "recipe/deploy/vendors.php",
    "chars": 1778,
    "preview": "<?php\n\nnamespace Deployer;\n\nset('composer_action', 'install');\nset('composer_options', '--verbose --prefer-dist --no-pro"
  },
  {
    "path": "recipe/deploy/writable.php",
    "chars": 6556,
    "preview": "<?php\n\nnamespace Deployer;\n\n// Used to make a writable directory by a server.\n// Used in `chown` and `acl` modes of {{wr"
  },
  {
    "path": "recipe/drupal7.php",
    "chars": 2031,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/common.php';\n\nadd('recipes', ['drupal7']);\n\ntask('deploy', [\n    'd"
  },
  {
    "path": "recipe/drupal8.php",
    "chars": 554,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/common.php';\n\nadd('recipes', ['drupal8']);\n\ntask('deploy', [\n    'd"
  },
  {
    "path": "recipe/flow_framework.php",
    "chars": 1043,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/common.php';\n\nadd('recipes', ['flow_framework']);\n\n// Flow-Framewor"
  },
  {
    "path": "recipe/fuelphp.php",
    "chars": 327,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/common.php';\n\nadd('recipes', ['fuelphp']);\n\n// FuelPHP 1.x shared d"
  },
  {
    "path": "recipe/joomla.php",
    "chars": 302,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/common.php';\n\nadd('recipes', ['joomla']);\n\nset('shared_files', ['co"
  },
  {
    "path": "recipe/laravel.php",
    "chars": 9056,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/common.php';\n\nadd('recipes', ['laravel']);\n\nset('shared_dirs', ['st"
  },
  {
    "path": "recipe/magento.php",
    "chars": 1103,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/common.php';\n\nadd('recipes', ['magento']);\n\n/**\n * Magento Configur"
  },
  {
    "path": "recipe/magento2.php",
    "chars": 21348,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/common.php';\nrequire_once __DIR__ . '/../contrib/cachetool.php';\n\nu"
  },
  {
    "path": "recipe/pimcore.php",
    "chars": 805,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/symfony.php';\n\nadd('recipes', ['pimcore']);\n\nadd('shared_dirs', ['p"
  },
  {
    "path": "recipe/prestashop.php",
    "chars": 824,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire_once __DIR__ . '/common.php';\n\nadd('recipes', ['prestashop']);\n\nset('shared_files', "
  },
  {
    "path": "recipe/provision/404.html",
    "chars": 2233,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width"
  },
  {
    "path": "recipe/provision/Caddyfile",
    "chars": 456,
    "preview": "{{domain}} {\n  root * {{deploy_path}}/current/{{public_path}}\n  encode zstd gzip\n  file_server\n  php_fastcgi * unix//run"
  },
  {
    "path": "recipe/provision/databases.php",
    "chars": 2966,
    "preview": "<?php\n\nnamespace Deployer;\n\nset('db_type', function () {\n    $supportedDbTypes = [\n        'none',\n        'mysql',\n    "
  },
  {
    "path": "recipe/provision/nodejs.php",
    "chars": 1123,
    "preview": "<?php\n\nnamespace Deployer;\n\nuse function Deployer\\Support\\escape_shell_argument;\n\nset('node_version', '--lts');\n\ndesc('I"
  },
  {
    "path": "recipe/provision/php.php",
    "chars": 3708,
    "preview": "<?php\n\nnamespace Deployer;\n\nset('php_version', function () {\n    $defaultPhpVersion = file_exists('composer.json')\n     "
  },
  {
    "path": "recipe/provision/user.php",
    "chars": 2876,
    "preview": "<?php\n\nnamespace Deployer;\n\nuse function Deployer\\Support\\parse_home_dir;\n\nset('sudo_password', function () {\n    return"
  },
  {
    "path": "recipe/provision/website.php",
    "chars": 2166,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Deployer;\n\nset('domain', function () {\n    return ask(' Domain: ', get('hostn"
  },
  {
    "path": "recipe/provision.php",
    "chars": 6430,
    "preview": "<?php\n\nnamespace Deployer;\n\nrequire __DIR__ . '/provision/databases.php';\nrequire __DIR__ . '/provision/nodejs.php';\nreq"
  },
  {
    "path": "recipe/shopware.php",
    "chars": 6201,
    "preview": "<?php\n/**\n * ## Usage\n *\n * Add `repository` to your _deploy.php_ file:\n *\n * ```php\n * set('repository', 'git@github.co"
  }
]

// ... and 138 more files (download for full content)

About this extraction

This page contains the full source code of the deployphp/deployer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 338 files (1014.5 KB), approximately 271.2k tokens, and a symbol index with 739 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!