commit bbb491d3d0ea51a48aaa52c0bc87e6b3dd8b5987 Author: Stefan Wieczorek Date: Tue May 30 11:57:37 2023 +0200 . diff --git a/.env b/.env new file mode 100755 index 0000000..4588b16 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +APP_ENV=dev +APP_VERSION='1.0.0' +APP_SECRET=d7e71b25a69f640224cfb1df1328454f +APP_DEV_GIT_REPOSITORY='https://github.com/swieczorek/tmp-delete-me.git' +APP_DEV_DEPLOY_DIRECTORY='/home/moby/htdocs/www.moby.io/' diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..b6e894b --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/.env.local +/.env.local.php +/.env.*.local +/bin/compiled/ +/data/keys/private.key +/config/secrets/prod/prod.decrypt.private.php +/public/bundles/ +/var/ +/vendor/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..612b727 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright © 2013—2023 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. \ No newline at end of file diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..914b290 --- /dev/null +++ b/bin/console @@ -0,0 +1,17 @@ +#!/usr/bin/env php8.2 +load(__DIR__.'/../.env'); + +error_reporting(-1); +ini_set('display_errors', '1'); + +try { + $privateKey = realpath(__DIR__.'/../data/keys/private.key'); + $compiler = new Compiler(); + $compiler->setPrivateKey($privateKey); + $compiler->compile($_ENV['APP_VERSION']); +} catch (\Exception $e) { + echo 'Failed to compile phar: ['.get_class($e).'] '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().PHP_EOL; + exit(1); +} diff --git a/bin/dploy b/bin/dploy new file mode 100755 index 0000000..589cbd7 --- /dev/null +++ b/bin/dploy @@ -0,0 +1,46 @@ +#!/usr/bin/env php8.2 + 0)); +} + +if (function_exists('ini_set')) { + @ini_set('display_errors', '1'); + if ($memoryLimit = getenv('DPLOY_MEMORY_LIMIT')) { + @ini_set('memory_limit', $memoryLimit); + } else { + $memoryInBytes = function ($value) { + $unit = strtolower(substr($value, -1, 1)); + $value = (int) $value; + switch($unit) { + case 'g': + $value *= 1024; + case 'm': + $value *= 1024; + case 'k': + $value *= 1024; + } + + return $value; + }; + $memoryLimit = trim(ini_get('memory_limit')); + if ($memoryLimit != -1 && $memoryInBytes($memoryLimit) < 1024 * 1024 * 1536) { + @ini_set('memory_limit', '1536M'); + } + unset($memoryInBytes); + } + unset($memoryLimit); +} + +require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; + +return function (array $context) { + $kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); + return new Application($kernel); +}; \ No newline at end of file diff --git a/changelog b/changelog new file mode 100755 index 0000000..e714d02 --- /dev/null +++ b/changelog @@ -0,0 +1,2 @@ +1.0.0 Stefan Wieczorek Thu, 01 Jun 2023 08:08:08 +- Initial Release diff --git a/composer.json b/composer.json new file mode 100755 index 0000000..102f64e --- /dev/null +++ b/composer.json @@ -0,0 +1,69 @@ +{ + "type": "project", + "license": "proprietary", + "minimum-stability": "stable", + "prefer-stable": true, + "require": { + "php": ">=8.1", + "ext-ctype": "*", + "ext-iconv": "*", + "composer/pcre": "^2.1 || ^3.1", + "symfony/console": "6.2.*", + "symfony/dotenv": "6.2.*", + "symfony/filesystem": "6.2.*", + "symfony/flex": "^2", + "symfony/framework-bundle": "6.2.*", + "symfony/http-client": "6.2.*", + "symfony/process": "6.2.*", + "symfony/runtime": "6.2.*", + "symfony/yaml": "6.2.*" + }, + "config": { + "allow-plugins": { + "php-http/discovery": true, + "symfony/flex": true, + "symfony/runtime": true + }, + "sort-packages": true + }, + "autoload": { + "psr-4": { + "App\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "App\\Tests\\": "tests/" + } + }, + "replace": { + "symfony/polyfill-ctype": "*", + "symfony/polyfill-iconv": "*", + "symfony/polyfill-php72": "*", + "symfony/polyfill-php73": "*", + "symfony/polyfill-php74": "*", + "symfony/polyfill-php80": "*", + "symfony/polyfill-php81": "*" + }, + "scripts": { + "auto-scripts": { + "cache:clear": "symfony-cmd", + "assets:install %PUBLIC_DIR%": "symfony-cmd" + }, + "post-install-cmd": [ + "@auto-scripts" + ], + "post-update-cmd": [ + "@auto-scripts" + ] + }, + "conflict": { + "symfony/symfony": "*" + }, + "extra": { + "symfony": { + "allow-contrib": false, + "require": "6.2.*" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100755 index 0000000..523b37d --- /dev/null +++ b/composer.lock @@ -0,0 +1,2689 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "cd797a173650b1f0fd01afdb5ea350e0", + "packages": [ + { + "name": "composer/pcre", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", + "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.1.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-11-17T09:50:14+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "symfony/cache", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "76babfd82f6bfd8f6cbe851a153b95dd074ffc53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/76babfd82f6bfd8f6cbe851a153b95dd074ffc53", + "reference": "76babfd82f6bfd8f6cbe851a153b95dd074ffc53", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2|^3", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/var-exporter": "^6.2.7" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-30T07:37:32+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "eeb71f04b6f7f34ca6d15633df82e014528b1632" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/eeb71f04b6f7f34ca6d15633df82e014528b1632", + "reference": "eeb71f04b6f7f34ca6d15633df82e014528b1632", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-01T10:32:47+00:00" + }, + { + "name": "symfony/config", + "version": "v6.2.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "249271da6f545d6579e0663374f8249a80be2893" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/249271da6f545d6579e0663374f8249a80be2893", + "reference": "249271da6f545d6579e0663374f8249a80be2893", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/filesystem": "^5.4|^6.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<5.4" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/yaml": "^5.4|^6.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v6.2.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-02-14T08:44:56+00:00" + }, + { + "name": "symfony/console", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "3582d68a64a86ec25240aaa521ec8bc2342b369b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/3582d68a64a86ec25240aaa521ec8bc2342b369b", + "reference": "3582d68a64a86ec25240aaa521ec8bc2342b369b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.4|^6.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-29T21:42:15+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "b6195feacceb88fc58a02b69522b569e4c6188ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/b6195feacceb88fc58a02b69522b569e4c6188ac", + "reference": "b6195feacceb88fc58a02b69522b569e4c6188ac", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/service-contracts": "^1.1.6|^2.0|^3.0", + "symfony/var-exporter": "^6.2.7" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.1", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<6.2", + "symfony/yaml": "<5.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.1", + "symfony/expression-language": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-30T13:35:57+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", + "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-01T10:25:55+00:00" + }, + { + "name": "symfony/dotenv", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "4481aa45be7a11d2335c1d5b5bbe2f0c6199b105" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/4481aa45be7a11d2335c1d5b5bbe2f0c6199b105", + "reference": "4481aa45be7a11d2335c1d5b5bbe2f0c6199b105", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "conflict": { + "symfony/console": "<5.4", + "symfony/process": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Dotenv\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Registers environment variables from a .env file", + "homepage": "https://symfony.com", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "source": "https://github.com/symfony/dotenv/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-10T10:06:03+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v6.2.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "e95f1273b3953c3b5e5341172dae838bacee11ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/e95f1273b3953c3b5e5341172dae838bacee11ee", + "reference": "e95f1273b3953c3b5e5341172dae838bacee11ee", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^5.4|^6.0" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v6.2.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-04-11T16:03:19+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "04046f35fd7d72f9646e721fc2ecb8f9c67d3339" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/04046f35fd7d72f9646e721fc2ecb8f9c67d3339", + "reference": "04046f35fd7d72f9646e721fc2ecb8f9c67d3339", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^5.4|^6.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-20T16:06:02+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "0ad3b6f1e4e2da5690fefe075cd53a238646d8dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ad3b6f1e4e2da5690fefe075cd53a238646d8dd", + "reference": "0ad3b6f1e4e2da5690fefe075cd53a238646d8dd", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-01T10:32:47+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v6.2.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "82b6c62b959f642d000456f08c6d219d749215b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/82b6c62b959f642d000456f08c6d219d749215b3", + "reference": "82b6c62b959f642d000456f08c6d219d749215b3", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v6.2.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-02-14T08:44:56+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.2.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "20808dc6631aecafbe67c186af5dcb370be3a0eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/20808dc6631aecafbe67c186af5dcb370be3a0eb", + "reference": "20808dc6631aecafbe67c186af5dcb370be3a0eb", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.2.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-02-16T09:57:23+00:00" + }, + { + "name": "symfony/flex", + "version": "v2.2.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/flex.git", + "reference": "2ff8465e7172790a47ab3c129f2b514eb2d8a286" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/flex/zipball/2ff8465e7172790a47ab3c129f2b514eb2d8a286", + "reference": "2ff8465e7172790a47ab3c129f2b514eb2d8a286", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.1", + "php": ">=8.0" + }, + "require-dev": { + "composer/composer": "^2.1", + "symfony/dotenv": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Flex\\Flex" + }, + "autoload": { + "psr-4": { + "Symfony\\Flex\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien.potencier@gmail.com" + } + ], + "description": "Composer plugin for Symfony", + "support": { + "issues": "https://github.com/symfony/flex/issues", + "source": "https://github.com/symfony/flex/tree/v2.2.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-02-18T08:03:15+00:00" + }, + { + "name": "symfony/framework-bundle", + "version": "v6.2.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/framework-bundle.git", + "reference": "df1899b6e9e52fc495daad6b4e307801860a66da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/df1899b6e9e52fc495daad6b4e307801860a66da", + "reference": "df1899b6e9e52fc495daad6b4e307801860a66da", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.1", + "symfony/cache": "^5.4|^6.0", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.2.8", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/error-handler": "^6.1", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-foundation": "^6.2", + "symfony/http-kernel": "^6.2.1", + "symfony/polyfill-mbstring": "~1.0", + "symfony/routing": "^5.4|^6.0" + }, + "conflict": { + "doctrine/annotations": "<1.13.1", + "doctrine/persistence": "<1.3", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "phpunit/phpunit": "<5.4.3", + "symfony/asset": "<5.4", + "symfony/console": "<5.4", + "symfony/dom-crawler": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", + "symfony/lock": "<5.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<6.2", + "symfony/mime": "<6.2", + "symfony/property-access": "<5.4", + "symfony/property-info": "<5.4", + "symfony/security-core": "<5.4", + "symfony/security-csrf": "<5.4", + "symfony/serializer": "<6.1", + "symfony/stopwatch": "<5.4", + "symfony/translation": "<6.2.8", + "symfony/twig-bridge": "<5.4", + "symfony/twig-bundle": "<5.4", + "symfony/validator": "<5.4", + "symfony/web-profiler-bundle": "<5.4", + "symfony/workflow": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.13.1|^2", + "doctrine/persistence": "^1.3|^2|^3", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^5.4|^6.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/console": "^5.4.9|^6.0.9", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dom-crawler": "^5.4|^6.0", + "symfony/dotenv": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/html-sanitizer": "^6.1", + "symfony/http-client": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/mailer": "^5.4|^6.0", + "symfony/messenger": "^6.2", + "symfony/mime": "^6.2", + "symfony/notifier": "^5.4|^6.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/process": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/rate-limiter": "^5.4|^6.0", + "symfony/security-bundle": "^5.4|^6.0", + "symfony/semaphore": "^5.4|^6.0", + "symfony/serializer": "^6.1", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/string": "^5.4|^6.0", + "symfony/translation": "^6.2.8", + "symfony/twig-bundle": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0", + "symfony/web-link": "^5.4|^6.0", + "symfony/workflow": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0", + "twig/twig": "^2.10|^3.0" + }, + "suggest": { + "ext-apcu": "For best performance of the system caches", + "symfony/console": "For using the console commands", + "symfony/form": "For using forms", + "symfony/property-info": "For using the property_info service", + "symfony/serializer": "For using the serializer service", + "symfony/validator": "For using validation", + "symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering", + "symfony/yaml": "For using the debug:config and lint:yaml commands" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\FrameworkBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/framework-bundle/tree/v6.2.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-04-01T11:12:43+00:00" + }, + { + "name": "symfony/http-client", + "version": "v6.2.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "7daf5d24c21a683164688b95bb73b7a4bd3b32fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/7daf5d24c21a683164688b95bb73b7a4bd3b32fc", + "reference": "7daf5d24c21a683164688b95bb73b7a4bd3b32fc", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-client-contracts": "^3", + "symfony/service-contracts": "^1.0|^2|^3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v6.2.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-04-11T16:03:19+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "df2ecd6cb70e73c1080e6478aea85f5f4da2c48b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/df2ecd6cb70e73c1080e6478aea85f5f4da2c48b", + "reference": "df2ecd6cb70e73c1080e6478aea85f5f4da2c48b", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-01T10:32:47+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "511a524affeefc191939348823ac75e9921c2112" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/511a524affeefc191939348823ac75e9921c2112", + "reference": "511a524affeefc191939348823ac75e9921c2112", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.1" + }, + "conflict": { + "symfony/cache": "<6.2" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/cache": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^5.4|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-29T21:42:15+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v6.2.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "02246510cf7031726f7237138d61b796b95799b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/02246510cf7031726f7237138d61b796b95799b3", + "reference": "02246510cf7031726f7237138d61b796b95799b3", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/error-handler": "^6.1", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/http-foundation": "^5.4.21|^6.2.7", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<5.4", + "symfony/cache": "<5.4", + "symfony/config": "<6.1", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<6.2", + "symfony/doctrine-bridge": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4", + "symfony/translation": "<5.4", + "symfony/twig-bridge": "<5.4", + "symfony/validator": "<5.4", + "twig/twig": "<2.13" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/config": "^6.1", + "symfony/console": "^5.4|^6.0", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dependency-injection": "^6.2", + "symfony/dom-crawler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/process": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/translation-contracts": "^1.1|^2|^3", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v6.2.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-04-13T16:41:43+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/process", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "75ed64103df4f6615e15a7fe38b8111099f47416" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/75ed64103df4f6615e15a7fe38b8111099f47416", + "reference": "75ed64103df4f6615e15a7fe38b8111099f47416", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-09T16:20:02+00:00" + }, + { + "name": "symfony/routing", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "69062e2823f03b82265d73a966999660f0e1e404" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/69062e2823f03b82265d73a966999660f0e1e404", + "reference": "69062e2823f03b82265d73a966999660f0e1e404", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "symfony/config": "<6.2", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12|^2", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.2", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "suggest": { + "symfony/config": "For using the all-in-one router or any loader", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-14T15:00:05+00:00" + }, + { + "name": "symfony/runtime", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/runtime.git", + "reference": "f8b0751b33888329be8f8f0481bb81d279ec4157" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/runtime/zipball/f8b0751b33888329be8f8f0481bb81d279ec4157", + "reference": "f8b0751b33888329be8f8f0481bb81d279ec4157", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": ">=8.1" + }, + "conflict": { + "symfony/dotenv": "<5.4" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "symfony/console": "^5.4|^6.0", + "symfony/dotenv": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Runtime\\": "", + "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Enables decoupling PHP applications from global state", + "homepage": "https://symfony.com", + "keywords": [ + "runtime" + ], + "support": { + "source": "https://github.com/symfony/runtime/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-14T15:48:35+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "a8c9cedf55f314f3a186041d19537303766df09a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a", + "reference": "a8c9cedf55f314f3a186041d19537303766df09a", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-01T10:32:47+00:00" + }, + { + "name": "symfony/string", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/193e83bbd6617d6b2151c37fff10fa7168ebddef", + "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.0" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/intl": "^6.2", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-20T16:06:02+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "d37ab6787be2db993747b6218fcc96e8e3bb4bd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/d37ab6787be2db993747b6218fcc96e8e3bb4bd0", + "reference": "d37ab6787be2db993747b6218fcc96e8e3bb4bd0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-29T21:42:15+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "8302bb670204500d492c6b8c595ee9a27da62cd6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/8302bb670204500d492c6b8c595ee9a27da62cd6", + "reference": "8302bb670204500d492c6b8c595ee9a27da62cd6", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-14T15:48:45+00:00" + }, + { + "name": "symfony/yaml", + "version": "v6.2.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "e8e6a1d59e050525f27a1f530aa9703423cb7f57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e8e6a1d59e050525f27a1f530aa9703423cb7f57", + "reference": "e8e6a1d59e050525f27a1f530aa9703423cb7f57", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v6.2.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-02-16T09:57:23+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=8.1", + "ext-ctype": "*", + "ext-iconv": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/config/bundles.php b/config/bundles.php new file mode 100755 index 0000000..49d3fb6 --- /dev/null +++ b/config/bundles.php @@ -0,0 +1,5 @@ + ['all' => true], +]; diff --git a/config/packages/cache.yaml b/config/packages/cache.yaml new file mode 100755 index 0000000..6899b72 --- /dev/null +++ b/config/packages/cache.yaml @@ -0,0 +1,19 @@ +framework: + cache: + # Unique name of your app: used to compute stable namespaces for cache keys. + #prefix_seed: your_vendor_name/app_name + + # The "app" cache stores to the filesystem by default. + # The data in this cache should persist between deploys. + # Other options include: + + # Redis + #app: cache.adapter.redis + #default_redis_provider: redis://localhost + + # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) + #app: cache.adapter.apcu + + # Namespaced pools use the above "app" backend by default + #pools: + #my.dedicated.cache: null diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml new file mode 100755 index 0000000..c7f062f --- /dev/null +++ b/config/packages/framework.yaml @@ -0,0 +1,29 @@ +# see https://symfony.com/doc/current/reference/configuration/framework.html +framework: + secret: '%env(APP_SECRET)%' + #csrf_protection: true + http_method_override: false + handle_all_throwables: true + + # Enables session support. Note that the session will ONLY be started if you read or write from it. + # Remove or comment this section to explicitly disable session support. + session: + handler_id: null + cookie_secure: auto + cookie_samesite: lax + storage_factory_id: session.storage.factory.native + + #esi: true + #fragments: true + php_errors: + log: true + + http_client: + default_options: + http_version: '2.0' + +when@test: + framework: + test: true + session: + storage_factory_id: session.storage.factory.mock_file diff --git a/config/packages/routing.yaml b/config/packages/routing.yaml new file mode 100755 index 0000000..4b766ce --- /dev/null +++ b/config/packages/routing.yaml @@ -0,0 +1,12 @@ +framework: + router: + utf8: true + + # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. + # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands + #default_uri: http://localhost + +when@prod: + framework: + router: + strict_requirements: null diff --git a/config/preload.php b/config/preload.php new file mode 100755 index 0000000..5ebcdb2 --- /dev/null +++ b/config/preload.php @@ -0,0 +1,5 @@ +getContainer()->get($id); + } + + protected function getContainer() + { + return $this->getApplication()->getContainer(); + } + + protected function getKernel() + { + $container = $this->getContainer(); + return $container->get('kernel'); + } + + protected function getProjectDirectory(): string + { + $kernel = $this->getKernel(); + return $kernel->getProjectDir(); + } +} \ No newline at end of file diff --git a/src/Command/DeployCommand.php b/src/Command/DeployCommand.php new file mode 100755 index 0000000..90af968 --- /dev/null +++ b/src/Command/DeployCommand.php @@ -0,0 +1,53 @@ +setName('deploy'); + $this->setHidden(false); + $this->setDescription('Deploys a branch or tag.'); + $this->addArgument('version', InputArgument::REQUIRED, 'version'); + $this->setHelp( + <<Examples: + +Deploying a branch: dploy deploy main +Deploying a tag: dploy deploy v1.0.0 +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + try { + $version = trim($input->getArgument('version')); + $config = new DeploymentConfig(); + $systemUserId = $config->getSystemUserId(); + if (0 == $systemUserId) { + throw new \Exception('Not allowed to run the command as root, use the site user.'); + } + $filesystem = new Filesystem(); + $configFile = $config->getConfigFile(); + if (true === $filesystem->exists($configFile)) { + $deployment = new Deployment($output, $config, $version); + $deployment->deploy(); + $output->writeln('Deployment has been completed!'); + } else { + throw new \Exception(sprintf('Config file "%s" does not exist. Did you run dploy setup $template?', $configFile)); + } + return Command::SUCCESS; + } catch (\Exception $e) { + $output->writeln(sprintf('%s', $e->getMessage())); + return Command::FAILURE; + } + } +} \ No newline at end of file diff --git a/src/Command/InitCommand.php b/src/Command/InitCommand.php new file mode 100755 index 0000000..88fff48 --- /dev/null +++ b/src/Command/InitCommand.php @@ -0,0 +1,107 @@ +setName('init'); + $this->setDescription('Setups the project directory structure.'); + $this->addArgument('application', InputArgument::OPTIONAL, 'Downloads a config for the application.', 'generic'); + $this->setHelp( + <<init command downloads a pre-configured config.yml from + +https://github.com/cloudpanel-io/dploy-application-templates + +dploy init laravel +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + try { + $applicationName = trim($input->getArgument('application')); + $filesystem = new Filesystem(); + $config = new DeploymentConfig(); + $systemUserId = $config->getSystemUserId(); + if (0 == $systemUserId) { + throw new \Exception('Not allowed to run the command as root, use the site user.'); + } + $configFile = $config->getConfigFile(); + if (false === $filesystem->exists($configFile)) { + $templates = $this->getTemplates(); + if (false === isset($templates[$applicationName])) { + throw new \Exception(sprintf('Template %s does not exist, available templates: %s', $applicationName, implode(',', array_keys($templates)))); + } + if (true === function_exists('xdebug_is_debugger_active') && true === xdebug_is_debugger_active()) { + $gitRepository = $_ENV['APP_DEV_GIT_REPOSITORY']; + $deployDirectory = $_ENV['APP_DEV_DEPLOY_DIRECTORY']; + } else { + $helper = $this->getHelper('question'); + $gitRepositoryQuestion = new Question('Git Repository: '); + $gitRepository = $helper->ask($input, $output, $gitRepositoryQuestion); + $output->writeln(sprintf('Deploy directory like: /home/%s/htdocs/www.domain.com', $config->getSystemUserName())); + $deployDirectoryQuestion = new Question('Deploy Directory: '); + $deployDirectory = $helper->ask($input, $output, $deployDirectoryQuestion); + } + $deployDirectory = rtrim($deployDirectory, '/'); + $template = str_replace(['{git_repository}', '{deploy_directory}'], [$gitRepository, $deployDirectory], $templates[$applicationName]); + $tmpFile = tmpfile(); + $tmpFilePath = stream_get_meta_data($tmpFile)['uri']; + file_put_contents($tmpFilePath, $template); + $configDirectory = $config->getConfigDirectory(); + $filesystem = new Filesystem(); + $filesystem->mkdir($configDirectory, 0770); + $filesystem->copy($tmpFilePath, $configFile); + $setup = new DeploymentSetup($config); + $setup->create(); + $output->writeln(sprintf('%s', sprintf('The config "%s" has been created.', $configFile))); + } else { + throw new \Exception(sprintf('Config file %s already exists, nothing to do.', $configFile)); + } + return Command::SUCCESS; + } catch (\Exception $e) { + $output->writeln(sprintf('%s', $e->getMessage())); + return Command::FAILURE; + } + } + + private function getTemplates(): array + { + $templates = []; + try { + $filesystem = new Filesystem(); + $tmpDirectory = sprintf('%s/%s', rtrim(sys_get_temp_dir(), '/'), uniqid()); + $gitCloneCommand = sprintf('/usr/bin/git clone %s %s', self::TEMPLATES_GITHUB_REPOSITORY, $tmpDirectory); + $process = Process::fromShellCommandline($gitCloneCommand); + $process->setTimeout(600); + $process->run(); + $directoryIterator = new \DirectoryIterator($tmpDirectory); + foreach ($directoryIterator as $fileInfo) { + $name = $fileInfo->getFilename(); + $filePath = $fileInfo->getPathname(); + if (false === empty($name) && true === is_file($filePath) && true === file_exists($filePath)) { + $templates[$name] = file_get_contents($filePath); + } + } + } catch (\Exception $e) { + throw $e; + } finally { + if (true === isset($tmpDirectory) && true === is_dir($tmpDirectory)) { + $filesystem->remove($tmpDirectory); + } + } + return $templates; + } +} \ No newline at end of file diff --git a/src/Command/ListCommand.php b/src/Command/ListCommand.php new file mode 100755 index 0000000..2eba77f --- /dev/null +++ b/src/Command/ListCommand.php @@ -0,0 +1,94 @@ +setName('list') + ->setDefinition([ + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, function () { + return array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces()); + }), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', function () { + return (new DescriptorHelper())->getFormats(); + }), + new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'), + ]) + ->setDescription('List commands') + ->setHelp(<<<'EOF' +The %command.name% command lists all commands: + + %command.full_name% + +You can also display the commands for a specific namespace: + + %command.full_name% test + +You can also output the information in other formats by using the --format option: + + %command.full_name% --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + %command.full_name% --raw +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $application = $this->getApplication(); + if ('' != $help = $application->getHelp()) { + $output->writeln($help.PHP_EOL); + } + $output->writeln('Usage:'); + $output->writeln(' command [options] [arguments]'.PHP_EOL); + $output->writeln('Available commands:'); + $commands = $application->getCommands(); + $width = $this->getColumnWidth($commands); + foreach ($commands as $command) { + if (false === ($command instanceof BaseCommand) || (true === $command->isHidden())) { + continue; + } + $name = $command->getName(); + $spacingWidth = $width - Helper::width($name); + $output->writeln(sprintf(' %s%s %s', $name, str_repeat(' ', $spacingWidth), $command->getDescription())); + } + return 0; + } + + private function getColumnWidth(array $commands): int + { + $widths = []; + foreach ($commands as $command) { + if (false === ($command instanceof BaseCommand)) { + continue; + } + if ($command instanceof BaseCommand) { + $widths[] = Helper::width($command->getName()); + foreach ($command->getAliases() as $alias) { + $widths[] = Helper::width($alias); + } + } else { + $widths[] = Helper::width($command); + } + } + return $widths ? max($widths) + 2 : 0; + } +} \ No newline at end of file diff --git a/src/Command/SelfUpdateCommand.php b/src/Command/SelfUpdateCommand.php new file mode 100755 index 0000000..a1b10a3 --- /dev/null +++ b/src/Command/SelfUpdateCommand.php @@ -0,0 +1,84 @@ +setName('self-update'); + $this->setDescription('Updates dploy to the latest version.'); + $this->addOption('channel', null, InputOption::VALUE_OPTIONAL, sprintf('Sets the channel to update dploy from, available channels: %s', implode(', ', Dploy::CHANNELS))); + $this->addOption('setVersion', null, InputOption::VALUE_OPTIONAL, 'Sets the specific version to update.'); + $this->setHelp( + <<self-update command checks dploy.cloudpanel.io for newer +versions of dploy and if found, installs the latest. + +dploy self-update +EOT); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + try { + $container = $this->getContainer(); + $dploy = $container->get('App\Dploy'); + $config = new Config(); + $channel = (string)$input->getOption('channel'); + if (true === empty($channel)) { + $channel = $config->get('channel'); + $channel = (false === empty($channel) ? $channel : Dploy::CHANNEL_STABLE); + } + $channels = $dploy->getChannels(); + if (false === in_array($channel, $channels)) { + throw new \Exception(sprintf('%s is not a valid channel, available channels: %s', $channel, implode(', ', Dploy::CHANNELS))); + } + $config->set('channel', $channel); + $localFilename = realpath($_SERVER['argv'][0]); + if (false === $localFilename) { + $localFilename = $_SERVER['argv'][0]; + } + if (false === file_exists($localFilename)) { + throw new \Exception(sprintf('Dploy update failed: the %s is not accessible.', $localFilename)); + } + $latest = $dploy->getLatest($channel); + $latestVersion = $latest['version'] ?? ''; + $currentVersion = $dploy->getVersion(); + $updateVersion = $input->getOption('setVersion'); + $updateVersion = (false === empty($updateVersion) ? $updateVersion : $latestVersion); + if ($currentVersion == $updateVersion) { + $output->writeln(sprintf('You already use the latest dploy version %s (%s channel).', $updateVersion, $channel)); + } else { + if ($currentVersion < $updateVersion) { + $output->writeln(sprintf('Upgrading from %s to %s (%s channel).', $currentVersion, $updateVersion, $channel)); + $output->writeln(''); + $downloadedFile = $dploy->downloadVersion($updateVersion, $output); + $publicKey = sprintf('%s/data/keys/public.key', $this->getProjectDirectory()); + $opensslPublicKey = openssl_pkey_get_public(file_get_contents($publicKey)); + if (false === $opensslPublicKey) { + throw new \RuntimeException(sprintf('Failed loading the public key from: %s', $publicKey)); + } + $signature = $dploy->getSignatureForVersion($updateVersion); + $verified = 1 === openssl_verify((string) file_get_contents($downloadedFile), $signature, $opensslPublicKey, OPENSSL_ALGO_SHA384); + if (false === $verified) { + throw new \RuntimeException('The phar signature did not match the file you downloaded, this means your public keys are outdated or that the phar file is corrupt/has been modified.'); + } + @copy($downloadedFile, $localFilename); + $output->writeln(str_repeat(PHP_EOL, 1)); + exit(Command::SUCCESS); + } + } + return Command::SUCCESS; + } catch (\Exception $e) { + $output->writeln(PHP_EOL.PHP_EOL.sprintf('%s', $e->getMessage())); + return Command::FAILURE; + } + } +} \ No newline at end of file diff --git a/src/Command/TestCommand.php b/src/Command/TestCommand.php new file mode 100755 index 0000000..e5bb4cb --- /dev/null +++ b/src/Command/TestCommand.php @@ -0,0 +1,46 @@ +setName('test'); + $this->setHidden(false); + $this->setDescription('dploy test'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + try { + + + + /* + $process = Process::fromShellCommandline($command, '/tmp/'); + $process->setTimeout(3600); + $process->run(function ($type, $buffer) use ($output) { + if (false === empty($buffer)) { + $output->write($buffer); + } + }); + if (false === $process->isSuccessful()) { + throw new \RuntimeException($process->getErrorOutput()); + } + */ + + //$output->writeln(sprintf('Muha: %s', rand(1,1000))); + + return Command::SUCCESS; + } catch (\Exception $e) { + $errorMessage = $e->getMessage(); + $output->writeln(sprintf('An error has occurred: "%s"', $errorMessage)); + return Command::FAILURE; + } + } +} \ No newline at end of file diff --git a/src/Compiler/Compiler.php b/src/Compiler/Compiler.php new file mode 100755 index 0000000..0fa8726 --- /dev/null +++ b/src/Compiler/Compiler.php @@ -0,0 +1,227 @@ +remove($compiledDirectory); + $filesystem->mkdir($compiledDirectory); + $pharFile = sprintf('%s/dploy.phar', $compiledDirectory); + $finalFile = sprintf('%s/dploy', $compiledDirectory); + $signatureFile = sprintf('%s/dploy.sig', $compiledDirectory); + if (file_exists($pharFile)) { + unlink($pharFile); + } + $phar = new \Phar($pharFile, 0, 'dploy.phar'); + $phar->setSignatureAlgorithm(\Phar::SHA512); + $phar->startBuffering(); + $finderSort = static function ($a, $b): int { + return strcmp(strtr($a->getRealPath(), '\\', '/'), strtr($b->getRealPath(), '\\', '/')); + }; + $finder = new Finder(); + $finder->files() + ->ignoreVCS(true) + ->name('*.php') + ->notName('Compiler.php') + ->notName('ClassLoader.php') + ->notName('InstalledVersions.php') + ->in(__DIR__.'/..') + ->sort($finderSort) + ; + foreach ($finder as $file) { + $this->addFile($phar, $file); + } + $envFile = new \SplFileInfo(__DIR__.'/../../.env'); + $this->addFile($phar, $envFile); + // Add vendor files + $finder = new Finder(); + $finder->files() + ->ignoreVCS(true) + ->notPath('/\/(composer\.(json|lock)|[A-Z]+\.md(?:own)?|\.gitignore|appveyor.yml|phpunit\.xml\.dist|phpstan\.neon\.dist|phpstan-config\.neon|phpstan-baseline\.neon)$/') + ->notPath('/bin\/(jsonlint|validate-json|simple-phpunit|phpstan|phpstan\.phar)(\.bat)?$/') + ->notPath('justinrainbow/json-schema/demo/') + ->notPath('justinrainbow/json-schema/dist/') + ->notPath('composer/installed.json') + ->notPath('composer/LICENSE') + ->notPath('keys/private.key') + ->notPath('bin/patch-type-declarations') + ->notPath('bin/var-dump-server') + ->notPath('bin/yaml-lint') + ->notPath('psr/cache/LICENSE.txt') + ->notPath('symfony/console/Resources/bin/hiddeninput.exe') + ->notPath('symfony/console/Resources/completion.bash') + ->notPath('symfony/console/Resources/completion.fish') + ->notPath('symfony/console/Resources/completion.zsh') + ->notPath('symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd') + ->notPath('symfony/error-handler/Resources/assets/') + ->notPath('symfony/var-dumper/Resources/css/htmlDescriptor.css') + ->notPath('symfony/var-dumper/Resources/js/htmlDescriptor.js') + ->notPath('symfony/runtime/Internal/autoload_runtime.template') + ->notPath('symfony/routing/Loader/schema/routing/routing-1.0.xsd') + ->notPath('symfony/framework-bundle/Resources/config/schema/symfony-1.0.xsd') + ->notPath('symfony/framework-bundle/Resources/config/routing/errors.xml') + ->exclude('Tests') + ->exclude('tests') + ->exclude('docs') + ->in(__DIR__.'/../../vendor/') + ->in(__DIR__.'/../../config/') + ->in(__DIR__.'/../../data/') + ->in(__DIR__.'/../../var/') + ->sort($finderSort) + ; + $extraFiles = []; + $unexpectedFiles = []; + foreach ($finder as $file) { + if (false !== ($index = array_search($file->getRealPath(), $extraFiles, true))) { + unset($extraFiles[$index]); + } elseif (!Preg::isMatch('{(^LICENSE$|\.php$)}', $file->getFilename())) { + //$unexpectedFiles[] = (string) $file; + } + if (Preg::isMatch('{\.php[\d.]*$}', $file->getFilename())) { + $this->addFile($phar, $file); + } else { + $this->addFile($phar, $file, false); + } + } + if (count($extraFiles) > 0) { + throw new \RuntimeException('These files were expected but not added to the phar, they might be excluded or gone from the source package:'.PHP_EOL.var_export($extraFiles, true)); + } + if (count($unexpectedFiles) > 0) { + throw new \RuntimeException('These files were unexpectedly added to the phar, make sure they are excluded or listed in $extraFiles:'.PHP_EOL.var_export($unexpectedFiles, true)); + } + $envFile = dirname(__FILE__).'/../../.env'; + $envFileContent = file_get_contents($envFile); + $this->addDployBin($phar); + $phar->addFromString('composer.json', ''); + $phar->addFromString('.env', $envFileContent); + $phar['.env']->chmod(0777); + $stub = $this->getStub(); + $phar->setStub($stub); + //$phar->compressFiles(\Phar::GZ); + $phar->stopBuffering(); + $privateKey = $this->getPrivateKey(); + $private = openssl_get_privatekey(file_get_contents($privateKey)); + openssl_sign((string)file_get_contents($pharFile), $signature, $private, OPENSSL_ALGO_SHA384); + file_put_contents($signatureFile, base64_encode($signature)); + rename($pharFile, $finalFile); + unset($phar); + } + + public function setPrivateKey(string $privateKey) + { + $this->privateKey = $privateKey; + } + + public function getPrivateKey() + { + return $this->privateKey; + } + + private function getRelativeFilePath(\SplFileInfo $file): string + { + $realPath = (string)$file->getRealPath(); + $pathPrefix = dirname(__DIR__, 2).DIRECTORY_SEPARATOR; + + $pos = strpos($realPath, $pathPrefix); + $relativePath = ($pos !== false) ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath; + + return strtr($relativePath, '\\', '/'); + } + + private function addFile(\Phar $phar, \SplFileInfo $file, bool $strip = true): void + { + $path = $this->getRelativeFilePath($file); + $content = file_get_contents((string) $file); + if ($strip) { + //$content = $this->stripWhitespace($content); + } elseif ('LICENSE' === $file->getFilename()) { + $content = "\n".$content."\n"; + } + if ($path === 'src/Composer/Composer.php') { + $content = strtr( + $content, + [ + '@package_version@' => $this->version, + '@package_branch_alias_version@' => $this->branchAliasVersion, + '@release_date@' => $this->versionDate->format('Y-m-d H:i:s'), + ] + ); + $content = Preg::replace('{SOURCE_VERSION = \'[^\']+\';}', 'SOURCE_VERSION = \'\';', $content); + } + $phar->addFromString($path, $content); + $phar[$path]->chmod(0777); + } + + private function addDployBin(\Phar $phar): void + { + $content = file_get_contents(__DIR__.'/../../bin/dploy'); + //$content = Preg::replace('{^#!/usr/bin/env php8.2\s*}', '', $content); + $phar->addFromString('bin/dploy', $content); + } + + private function stripWhitespace(string $source): string + { + if (!function_exists('token_get_all')) { + return $source; + } + + $output = ''; + foreach (token_get_all($source) as $token) { + if (is_string($token)) { + $output .= $token; + } elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT])) { + $output .= str_repeat("\n", substr_count($token[1], "\n")); + } elseif (T_WHITESPACE === $token[0]) { + // reduce wide spaces + $whitespace = Preg::replace('{[ \t]+}', ' ', $token[1]); + // normalize newlines to \n + $whitespace = Preg::replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); + // trim leading spaces + $whitespace = Preg::replace('{\n +}', "\n", $whitespace); + $output .= $whitespace; + } else { + $output .= $token[1]; + } + } + + return $output; + } + + private function getStub(): string + { + $stub = <<<'EOF' +#!/usr/bin/env php8.2 + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Composer\Autoload\ClassLoader; + +function includeIfExists(string $file): ?ClassLoader +{ + return file_exists($file) ? include $file : null; +} + +if ((!$loader = includeIfExists(__DIR__.'/../../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../../../../autoload.php'))) { + echo 'You must set up the project dependencies using `composer install`'.PHP_EOL. + 'See https://getcomposer.org/download/ for instructions on installing Composer'.PHP_EOL; + exit(1); +} + +return $loader; diff --git a/src/Console/Application.php b/src/Console/Application.php new file mode 100755 index 0000000..6509ceb --- /dev/null +++ b/src/Console/Application.php @@ -0,0 +1,96 @@ +kernel = $kernel; + parent::__construct($this->kernel); + } + public function doRun(InputInterface $input, OutputInterface $output): int + { + $this->setApplicationNameAndVersion(); + return parent::doRun($input, $output); + } + private function setApplicationNameAndVersion(): void + { + $version = Dploy::getVersion(); + $this->setName(self::APPLICATION_NAME); + $this->setVersion($version); + } + private function init() + { + if ($this->initialized) { + return; + } + $this->initialized = true; + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } + public function getHelp(): string + { + return self::APPLICATION_LOGO . parent::getHelp(); + } + public function getLongVersion(): string + { + $name = $this->getName(); + $version = $this->getVersion(); + $longVersion = sprintf('%s version %s', $name, $version); + return $longVersion; + + } + protected function getDefaultCommands(): array + { + $listCommand = new ListCommand(); + $listCommand->setHidden(true); + $commands = [ + new HelpCommand(), + $listCommand, + new CompleteCommand(), + new DumpCompletionCommand(), + new InitCommand(), + new DeployCommand(), + ]; + if (true === IS_PHAR) { + $commands[] = new SelfUpdateCommand(); + } + return $commands; + } + public function getCommands(): array + { + return $this->getDefaultCommands(); + } + public function getContainer(): ContainerInterface + { + return $this->kernel->getContainer(); + } +} \ No newline at end of file diff --git a/src/Controller/.gitignore b/src/Controller/.gitignore new file mode 100755 index 0000000..e69de29 diff --git a/src/Deployment/Command/CommandExecutor.php b/src/Deployment/Command/CommandExecutor.php new file mode 100755 index 0000000..60b028b --- /dev/null +++ b/src/Deployment/Command/CommandExecutor.php @@ -0,0 +1,31 @@ +runInBackground(); + $process = Process::fromShellCommandline($command->getCommand(), '/tmp/'); + $process->setCommand($command); + if (true === $runInBackground) { + $process->start(); + } else { + $process->setTimeout($timeout); + $process->run(); + if (false === $process->isSuccessful()) { + throw new \RuntimeException($process->getErrorOutput()); + } + } + } catch (\Exception $e) { + $fullCommand = $command->getCommand(); + $errorMessage = sprintf('Command "%s : %s" failed, error message: %s', $command->getName(), $fullCommand, $e->getMessage()); + throw new \Exception($errorMessage); + } + } +} \ No newline at end of file diff --git a/src/Deployment/Config.php b/src/Deployment/Config.php new file mode 100755 index 0000000..d50fa4e --- /dev/null +++ b/src/Deployment/Config.php @@ -0,0 +1,117 @@ +getValue('git_repository'); + } + + public function getDeployDirectory(): ?string + { + $deployConfig = $this->getValue('deploy'); + $deployDirectory = $deployConfig['directory'] ?? ''; + return $deployDirectory; + } + + public function getOverlaysDirectory(): string + { + $deployConfigDirectory = $this->getConfigDirectory(); + $overlaysDirectory = sprintf('%s/overlays', $deployConfigDirectory); + return $overlaysDirectory; + } + + public function getConfigDirectory(): string + { + $systemUserName = $this->getSystemUserName(); + $configDirectory = sprintf('/home/%s/.dploy', $systemUserName); + return $configDirectory; + } + + public function getHomeDirectory() + { + $homeDirectory = $_SERVER['HOME'] ?? ''; + return $homeDirectory; + } + + public function getConfigFile(): string + { + $configDirectory = $this->getConfigDirectory(); + $configFile = sprintf('%s/config.yml', $configDirectory); + return $configFile; + } + + public function getReleasesDirectory(): string + { + $deployDirectory = $this->getDeployDirectory(); + $releasesDirectory = sprintf('%s/releases', $deployDirectory); + return $releasesDirectory; + } + + public function getSharedDirectory(): string + { + $deployDirectory = $this->getDeployDirectory(); + $sharedDirectory = sprintf('%s/shared', $deployDirectory); + return $sharedDirectory; + } + + public function getSharedDirectories(): array + { + $deployConfig = $this->getValue('deploy'); + $sharedDirectories = (true == isset($deployConfig['shared_directories']) ? (array)$deployConfig['shared_directories'] : []); + return $sharedDirectories; + } + + public function getBeforeDeployCommands(): array + { + $deployConfig = $this->getValue('deploy'); + $beforeDeployCommands = (true == isset($deployConfig['before_commands']) ? (array)$deployConfig['before_commands'] : []); + return $beforeDeployCommands; + } + + public function getAfterDeployCommands(): array + { + $deployConfig = $this->getValue('deploy'); + $afterDeployCommands = (true == isset($deployConfig['after_commands']) ? (array)$deployConfig['after_commands'] : []); + return $afterDeployCommands; + } + + private function parseConfigFile(): void + { + if (true === is_null($this->configParsed)) { + $configFile = $this->getConfigFile(); + $data = Yaml::parseFile($configFile); + if (true === isset($data['project'])) { + $this->data = $data['project']; + } + $this->configParsed = true; + } + } + + private function getValue(string $key): mixed + { + $this->parseConfigFile(); + $value = $this->data[$key] ?? ''; + return $value; + } + + public function getSystemUserName(): string + { + $systemUserId = $this->getSystemUserId(); + $processUser = posix_getpwuid($systemUserId); + $systemUserName = $processUser['name']; + return $systemUserName; + } + public function getSystemUserId(): int + { + $systemUserId = posix_geteuid(); + return $systemUserId; + } +} \ No newline at end of file diff --git a/src/Deployment/Deployment.php b/src/Deployment/Deployment.php new file mode 100755 index 0000000..3c2a353 --- /dev/null +++ b/src/Deployment/Deployment.php @@ -0,0 +1,131 @@ +validate(); + $this->executeTasks(); + } + + private function executeTasks(): void + { + $this->addTasks(); + foreach ($this->tasks as $task) { + $this->output->writeln(sprintf('%s ...', $task->getDescription())); + $task->run(); + } + } + + private function addTasks(): void + { + $gitCloneRepositoryTask = new GitCloneRepositoryTask($this); + $copyOverlayFilesTask = new CopyOverlayFilesTask($this); + $symlinkSharedDirectoriesTask = new SymlinkSharedDirectoriesTask($this); + $this->tasks = array_merge([ + $gitCloneRepositoryTask, + $copyOverlayFilesTask, + $symlinkSharedDirectoriesTask + ], $this->tasks); + $beforeCommands = $this->config->getBeforeDeployCommands(); + if (false === empty($beforeCommands)) { + foreach ($beforeCommands as $command) { + if (false === empty($command)) { + $executeCommandTask = new ExecuteCommandTask($this); + $executeCommandTask->setCommand($command); + $this->tasks[] = $executeCommandTask; + } + } + } + //$setPermissionsTask = new SetPermissionsTask($this); + $releaseSwitchTask = new ReleaseSwitchTask($this); + $this->tasks = array_merge($this->tasks, [$releaseSwitchTask]); + $afterCommands = $this->config->getAfterDeployCommands(); + if (false === empty($afterCommands)) { + foreach ($afterCommands as $command) { + if (false === empty($command)) { + $executeCommandTask = new ExecuteCommandTask($this); + $executeCommandTask->setCommand($command); + $this->tasks[] = $executeCommandTask; + } + } + } + $cleanUpReleasesTask = new CleanUpReleasesTask($this); + $this->tasks[] = $cleanUpReleasesTask; + } + + public function getVersion(): string + { + return $this->version; + } + + public function getOutput(): OutputInterface + { + return $this->output; + } + + public function getReleaseName(): string + { + if (true === is_null($this->releaseName)) { + $dateTime = new \DateTime('now'); + $this->releaseName = sprintf('%s-%s', $dateTime->format('Y-m-d-H-i-s'), $this->version); + } + return $this->releaseName; + } + + public function getReleaseDirectory(): string + { + $releaseName = $this->getReleaseName(); + $releasesDirectory = $this->config->getReleasesDirectory(); + $releaseDirectory = sprintf('%s/%s', $releasesDirectory, $releaseName); + return $releaseDirectory; + } + + public function getConfig(): Config + { + return $this->config; + } + + private function validate(): void + { + $filesystem = new Filesystem(); + $deployDirectory = $this->config->getDeployDirectory(); + $systemUserName = $this->config->getSystemUserName(); + if (false === $filesystem->exists($deployDirectory)) { + throw new \Exception(sprintf('Deploy directory "%s" does not exist.', $deployDirectory)); + } + if (false === str_starts_with($deployDirectory, sprintf('/home/%s/htdocs', $systemUserName))) { + throw new \Exception(sprintf('System User "%s" is not part of the deploy directory: "%s"', $systemUserName, $deployDirectory)); + } + $overlaysDirectory = $this->config->getOverlaysDirectory(); + if (false === $filesystem->exists($overlaysDirectory)) { + throw new \Exception(sprintf('Overlays directory "%s" does not exist.', $overlaysDirectory)); + } + $gitRepository = $this->config->getGitRepository(); + if (true === empty($gitRepository)) { + throw new \Exception('Git repository cannot be empty.'); + } + } +} \ No newline at end of file diff --git a/src/Deployment/Setup.php b/src/Deployment/Setup.php new file mode 100755 index 0000000..a5adfaf --- /dev/null +++ b/src/Deployment/Setup.php @@ -0,0 +1,53 @@ +config->getGitRepository(); + $deployDirectory = $this->config->getDeployDirectory(); + if (true === empty($gitRepository)) { + throw new \Exception('git repository cannot be empty.'); + } + if (true === empty($deployDirectory)) { + throw new \Exception('deploy directory cannot be empty.'); + } + $filesystem = new Filesystem(); + $configDirectory = $this->config->getConfigDirectory(); + if (false === $filesystem->exists($configDirectory)) { + $filesystem->mkdir($configDirectory, self::DIRECTORY_CHMOD); + } + $releasesDirectory = $this->config->getReleasesDirectory(); + if (false === $filesystem->exists($releasesDirectory)) { + $filesystem->mkdir($releasesDirectory, self::DIRECTORY_CHMOD); + } + $overlaysDirectory = $this->config->getOverlaysDirectory(); + if (false === $filesystem->exists($overlaysDirectory)) { + $filesystem->mkdir($overlaysDirectory, self::DIRECTORY_CHMOD); + } + $sharedDirectory = $this->config->getSharedDirectory(); + if (false === $filesystem->exists($sharedDirectory)) { + $filesystem->mkdir($sharedDirectory, self::DIRECTORY_CHMOD); + } + $sharedDirectories = $this->config->getSharedDirectories(); + if (false === empty($sharedDirectories)) { + foreach ($sharedDirectories as $directory) { + $directory = sprintf('%s/%s', $sharedDirectory, $directory); + if (false === $filesystem->exists($directory)) { + $filesystem->mkdir($directory, self::DIRECTORY_CHMOD); + } + } + } + } +} \ No newline at end of file diff --git a/src/Deployment/Task/CleanUpReleases.php b/src/Deployment/Task/CleanUpReleases.php new file mode 100755 index 0000000..e9ce04d --- /dev/null +++ b/src/Deployment/Task/CleanUpReleases.php @@ -0,0 +1,19 @@ +getDeployment(); + $config = $deployment->getConfig(); + $releasesDirectory = $config->getReleasesDirectory(); + $command = sprintf('cd %s && ls -t | tail -n +4 | xargs rm -rf', $releasesDirectory); + $this->runCommand($command); + } +} \ No newline at end of file diff --git a/src/Deployment/Task/CopyOverlayFiles.php b/src/Deployment/Task/CopyOverlayFiles.php new file mode 100755 index 0000000..5b8a79e --- /dev/null +++ b/src/Deployment/Task/CopyOverlayFiles.php @@ -0,0 +1,28 @@ +getDeployment(); + $config = $deployment->getConfig(); + $releaseDirectory = $deployment->getReleaseDirectory(); + $overlaysDirectory = $config->getOverlaysDirectory(); + $filesystem = new Filesystem(); + $objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($overlaysDirectory), \RecursiveIteratorIterator::SELF_FIRST); + foreach($objects as $object){ + if (true === $object->isFile()) { + $originFile = $object->getRealPath(); + $targetFile = str_replace($overlaysDirectory, $releaseDirectory, $originFile); + $filesystem->copy($originFile, $targetFile); + } + } + } +} \ No newline at end of file diff --git a/src/Deployment/Task/ExecuteCommand.php b/src/Deployment/Task/ExecuteCommand.php new file mode 100755 index 0000000..a24a618 --- /dev/null +++ b/src/Deployment/Task/ExecuteCommand.php @@ -0,0 +1,51 @@ +getDeployment(); + $output = $deployment->getOutput(); + $command = $this->getCommand(); + $process = Process::fromShellCommandline($command, '/tmp/'); + $process->setTimeout(3600); + $process->run(function ($type, $buffer) use ($output) { + if (false === empty($buffer)) { + $output->write($buffer); + } + }); + if (false === $process->isSuccessful()) { + throw new \RuntimeException($process->getErrorOutput()); + } + } + + public function getDescription(): string + { + $command = $this->getCommand(); + $this->description = sprintf('%s: %s', $this->description, $command); + return $this->description; + } + + public function setCommand(string $command): void + { + $this->command = $command; + } + + public function getCommand(): string + { + $deployment = $this->getDeployment(); + $releaseDirectory = $deployment->getReleaseDirectory(); + if (true === str_contains($this->command, '{release_directory}')) { + $this->command = str_replace('{release_directory}', $releaseDirectory, $this->command); + } + return $this->command; + } +} \ No newline at end of file diff --git a/src/Deployment/Task/GitCloneRepository.php b/src/Deployment/Task/GitCloneRepository.php new file mode 100755 index 0000000..3909c55 --- /dev/null +++ b/src/Deployment/Task/GitCloneRepository.php @@ -0,0 +1,38 @@ +getDeployment(); + $config = $deployment->getConfig(); + $gitRepository = $config->getGitRepository(); + $version = $deployment->getVersion(); + $releaseDirectory = $deployment->getReleaseDirectory(); + $command = sprintf('/usr/bin/git clone -c advice.detachedHead=false --progress --depth 1 --branch %s %s %s', escapeshellarg($version), escapeshellarg($gitRepository), escapeshellarg($releaseDirectory)); + $output = $deployment->getOutput(); + $process = Process::fromShellCommandline($command, '/tmp/'); + $process->setTimeout(600); + $process->run(function ($type, $buffer) use ($output) { + if (false === empty($buffer)) { + $output->write($buffer); + } + }); + if (false === $process->isSuccessful()) { + throw new \RuntimeException($process->getErrorOutput()); + } + $gitDirectory = sprintf('%s/.git', $releaseDirectory); + $filesystem = new Filesystem(); + if (true === $filesystem->exists($gitDirectory)) { + $filesystem->remove($gitDirectory); + } + } +} \ No newline at end of file diff --git a/src/Deployment/Task/ReleaseSwitch.php b/src/Deployment/Task/ReleaseSwitch.php new file mode 100755 index 0000000..d63b1d8 --- /dev/null +++ b/src/Deployment/Task/ReleaseSwitch.php @@ -0,0 +1,21 @@ +getDeployment(); + $config = $deployment->getConfig(); + $deployDirectory = $config->getDeployDirectory(); + $releaseDirectory = $deployment->getReleaseDirectory(); + $currentDirectory = sprintf('%s/current', $deployDirectory); + $command = sprintf('ln -sfn %s %s', $releaseDirectory, $currentDirectory); + $this->runCommand($command); + } +} \ No newline at end of file diff --git a/src/Deployment/Task/SetPermissions.php b/src/Deployment/Task/SetPermissions.php new file mode 100755 index 0000000..a996543 --- /dev/null +++ b/src/Deployment/Task/SetPermissions.php @@ -0,0 +1,26 @@ +getDeployment(); + $releaseDirectory = $deployment->getReleaseDirectory(); + $command = sprintf('/usr/bin/find %s -type d -exec chmod %s {} \; && /usr/bin/find %s -type f -exec chmod %s {} \;', + escapeshellarg($releaseDirectory), + self::DIRECTORY_CHMOD, + escapeshellarg($releaseDirectory), + self::FILE_CHMOD + ); + $this->runCommand($command); + } +} \ No newline at end of file diff --git a/src/Deployment/Task/SymlinkSharedDirectories.php b/src/Deployment/Task/SymlinkSharedDirectories.php new file mode 100755 index 0000000..c8c437e --- /dev/null +++ b/src/Deployment/Task/SymlinkSharedDirectories.php @@ -0,0 +1,28 @@ +getDeployment(); + $config = $deployment->getConfig(); + $releaseDirectory = $deployment->getReleaseDirectory(); + $sharedDirectories = $config->getSharedDirectories(); + if (false === empty($sharedDirectories)) { + foreach ($sharedDirectories as $sharedDirectory) { + $sharedDirectory = rtrim(ltrim($sharedDirectory, '/'), '/'); + $sharedDirectoryPath = sprintf('%s/%s', rtrim($releaseDirectory, '/'), $sharedDirectory); + $deleteDestinationCommand = sprintf('rm -rf %s', $sharedDirectoryPath); + $this->runCommand($deleteDestinationCommand); + $symlinkCommand = sprintf('cd %s && ln -sfrn ../../shared/%s %s', $releaseDirectory, $sharedDirectory, $sharedDirectory); + $this->runCommand($symlinkCommand); + } + } + } +} \ No newline at end of file diff --git a/src/Deployment/Task/Task.php b/src/Deployment/Task/Task.php new file mode 100755 index 0000000..b6fb353 --- /dev/null +++ b/src/Deployment/Task/Task.php @@ -0,0 +1,41 @@ +description; + } + + public function getDeployment(): Deployment + { + return $this->deployment; + } + + protected function runCommand(string $command, $timeout = 900) + { + $process = Process::fromShellCommandline($command, '/tmp/'); + $process->setTimeout($timeout); + $process->run(); + if (false === $process->isSuccessful()) { + throw new \RuntimeException($process->getErrorOutput()); + } + } +} \ No newline at end of file diff --git a/src/Dploy.php b/src/Dploy.php new file mode 100755 index 0000000..4b69e0e --- /dev/null +++ b/src/Dploy.php @@ -0,0 +1,111 @@ +setFormat('verbose'); + $this->percentageDownloaded = 0; + $response = $this->httpClient->request('GET', $remoteFile, [ + 'on_progress' => function (int $downloadedInBytes, int $downloadFilesizeInBytes, array $info) use ($progressBar): void { + if ($downloadedInBytes > 0) { + $downloadedInMegabyte = round($downloadedInBytes/1000000); + $downloadFilesizeInMegabyte = round($downloadFilesizeInBytes/1000000); + $percentageDownloaded = (int)round(($downloadedInMegabyte/$downloadFilesizeInMegabyte)*100); + if ($percentageDownloaded > 0 && $percentageDownloaded > $this->percentageDownloaded) { + $this->percentageDownloaded = $percentageDownloaded; + $progressBar->setProgress($percentageDownloaded); + } + } + }, + ]); + if (200 == $response->getStatusCode()) { + $this->tmpFile = tempnam(sys_get_temp_dir(), ''); + $tmpFile = sprintf('%s.phar', $this->tmpFile); + rename($this->tmpFile, $tmpFile); + $this->tmpFile = $tmpFile; + $body = $response->getContent(); + file_put_contents($this->tmpFile, $body); + $downloadedFile = $this->tmpFile; + } else { + throw new \Exception(sprintf('Cannot download file %s, status code: %s', $remoteFile, $response->getStatusCode())); + } + return $downloadedFile; + } + + public function getSignatureForVersion(string $version): string + { + $signature = ''; + $signatureFile = sprintf('%s/download/%s/dploy.sig', self::BASE_URL, $version); + $response = $this->httpClient->request('GET', $signatureFile, ['timeout' => self::REQUEST_TIMEOUT]); + $statusCode = $response->getStatusCode(); + if (200 == $statusCode) { + $signature = (string)$response->getContent(); + $signature = base64_decode($signature); + } else { + throw new \Exception(sprintf('Signature file %s not available.', $signatureFile)); + } + return $signature; + } + public function getLatest(string $channel) + { + $latest = $this->getVersionsData($channel); + return $latest; + } + + private function getVersionsData(string $channel): array + { + $requestUrl = sprintf('%s/versions.json', self::BASE_URL); + $response = $this->httpClient->request('GET', $requestUrl, ['timeout' => self::REQUEST_TIMEOUT]); + $statusCode = $response->getStatusCode(); + if (200 == $statusCode) { + $versionsData = (array)$response->toArray(); + if (true === isset($versionsData[$channel]) && false === empty($versionsData[$channel])) { + $versionsData = $versionsData[$channel]; + return $versionsData; + } else { + throw new \Exception(sprintf('No versions data available for channel %s.', $channel)); + } + } else { + throw new \Exception(sprintf('Versions file %s not available.', $requestUrl)); + } + } + + static public function getChannels(): array + { + return self::CHANNELS; + } + + static public function getVersion(): string + { + $version = $_ENV['APP_VERSION'] ?? ''; + return $version; + } + + public function __destruct() + { + if (true === isset($this->tmpFile) && true === file_exists($this->tmpFile)) { + @unlink($this->tmpFile); + } + } +} \ No newline at end of file diff --git a/src/Kernel.php b/src/Kernel.php new file mode 100755 index 0000000..f4d320d --- /dev/null +++ b/src/Kernel.php @@ -0,0 +1,38 @@ +getHomeDirectory(); + $cacheDir = sprintf('%s/.dploy/.app/cache', $homeDirectory); + return $cacheDir; + } + + public function getLogDir(): string + { + $homeDirectory = $this->getHomeDirectory(); + $logDir = sprintf('%s/.dploy/.app/logs', $homeDirectory); + return $logDir; + } +} \ No newline at end of file diff --git a/src/SelfUpdate/Config.php b/src/SelfUpdate/Config.php new file mode 100755 index 0000000..f66c6ed --- /dev/null +++ b/src/SelfUpdate/Config.php @@ -0,0 +1,60 @@ +get('home-directory'); + $dployDirectory = sprintf('%s/.dploy', $homeDirectory); + return $dployDirectory; + case 'cache-directory': + $dployDirectory = $this->get('dploy-directory'); + $cacheDirectory = sprintf('/home/%s/.cache', $dployDirectory); + return $cacheDirectory; + case 'home-directory': + $homeDirectory = $_SERVER['HOME'] ?? ''; + return $homeDirectory; + case 'channel': + $channel = ''; + $dployDirectory = $this->get('dploy-directory'); + $channelFile = sprintf('%s/.channel', $dployDirectory); + if (true === file_exists($channelFile)) { + $channel = trim(file_get_contents($channelFile)); + } + return $channel; + case 'channel-file': + $dployDirectory = $this->get('dploy-directory'); + $channelFile = sprintf('%s/.channel', $dployDirectory); + return $channelFile; + } + } + + public function set(string $key, string $value): void + { + $filesystem = new Filesystem(); + $dployDirectory = $this->get('dploy-directory'); + if (false === file_exists($dployDirectory)) { + $filesystem->mkdir($dployDirectory, 0770); + } + switch ($key) { + case 'channel': + $channelFile = $this->get('channel-file'); + file_put_contents($channelFile, $value); + break; + + } + } + + static public function getSystemUserName(): string + { + $processUser = posix_getpwuid(posix_geteuid()); + $systemUserName = $processUser['name']; + return $systemUserName; + } +} \ No newline at end of file diff --git a/src/Util/Retry.php b/src/Util/Retry.php new file mode 100755 index 0000000..bcf4def --- /dev/null +++ b/src/Util/Retry.php @@ -0,0 +1,23 @@ +