From e3dc24a7a36f9792176ef2789051fcdb71230cc1 Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Sun, 27 Sep 2026 23:33:48 +0100 Subject: [PATCH 1/5] chore(legacy): remove the legacy PHP installer and self-update The legacy CLI now only runs through the Go wrapper's embedded PHP binary, so the pre-5.x installer and self-update code paths are unused. Removed: - dist/ (installer.php, manifest.json, dev-build-index.php), the installer test and scripts, and .platform.app.yaml (dev-build hosting) - self:update, self:release and self:stats, with SelfUpdater, SelfUpdateChecker and ManifestStrategy - the installer_url, manifest_url, github_repo, release_branch and updates.* legacy config keys - the padraic/phar-updater and padraic/humbug_get_contents dependencies - the installer config check in self:build - the "Legacy installer" README section The https://platform.sh/cli/installer and manifest.json URLs are hosted separately and are not affected. Co-Authored-By: Claude Opus 5.5 --- .../update-message-install-detection.md | 2 +- internal/config/platformsh-cli.yaml | 1 - internal/config/upsun-cli.yaml | 1 - internal/legacy/cert_store_windows_test.go | 1 - legacy/.php-cs-fixer.dist.php | 1 - legacy/.platform.app.yaml | 49 - legacy/README.md | 15 - legacy/composer.json | 8 - legacy/composer.lock | 142 +-- legacy/config-defaults.yaml | 21 +- legacy/config.yaml | 4 - legacy/config/services.yaml | 2 - legacy/dist/README.md | 11 - legacy/dist/dev-build-index.php | 164 --- legacy/dist/installer.php | 1057 ----------------- legacy/dist/manifest.json | 208 ---- legacy/phpstan-baseline.neon | 264 ---- legacy/phpstan.neon | 4 - legacy/scripts/test/build-and-install.sh | 38 - legacy/scripts/test/installer.sh | 5 - legacy/src/Application.php | 7 - legacy/src/Command/Self/SelfBuildCommand.php | 50 - .../src/Command/Self/SelfReleaseCommand.php | 490 -------- legacy/src/Command/Self/SelfStatsCommand.php | 87 -- legacy/src/Command/Self/SelfUpdateCommand.php | 56 - .../Rector/InjectCommandServicesRector.php | 2 - legacy/src/SelfUpdate/ManifestStrategy.php | 283 ----- legacy/src/Service/SelfUpdateChecker.php | 129 -- legacy/src/Service/SelfUpdater.php | 168 --- legacy/tests/InstallerTest.php | 100 -- 30 files changed, 4 insertions(+), 3366 deletions(-) delete mode 100644 legacy/.platform.app.yaml delete mode 100644 legacy/dist/README.md delete mode 100644 legacy/dist/dev-build-index.php delete mode 100644 legacy/dist/installer.php delete mode 100644 legacy/dist/manifest.json delete mode 100755 legacy/scripts/test/build-and-install.sh delete mode 100755 legacy/scripts/test/installer.sh delete mode 100644 legacy/src/Command/Self/SelfReleaseCommand.php delete mode 100644 legacy/src/Command/Self/SelfStatsCommand.php delete mode 100644 legacy/src/Command/Self/SelfUpdateCommand.php delete mode 100644 legacy/src/SelfUpdate/ManifestStrategy.php delete mode 100644 legacy/src/Service/SelfUpdateChecker.php delete mode 100644 legacy/src/Service/SelfUpdater.php delete mode 100644 legacy/tests/InstallerTest.php diff --git a/docs/design/update-message-install-detection.md b/docs/design/update-message-install-detection.md index d5166f922..e9ffcb9c6 100644 --- a/docs/design/update-message-install-detection.md +++ b/docs/design/update-message-install-detection.md @@ -56,7 +56,7 @@ is shown even when the user cannot act on it usefully: ## Non-goals - Silent or unattended self-update. Updates are only ever performed after an - explicit interactive prompt. `self:update` (the PHP command) stays disabled. + explicit interactive prompt. The PHP `self:update` command has been removed. - Auto-running privileged or remote-code upgrades (`sudo`, `curl … | sh`). For those channels we print the command rather than executing it (see Phase 2). - Changing the network-check throttle, the CI gate, or the TTY gate. diff --git a/internal/config/platformsh-cli.yaml b/internal/config/platformsh-cli.yaml index e3860e3ab..edd4a6648 100644 --- a/internal/config/platformsh-cli.yaml +++ b/internal/config/platformsh-cli.yaml @@ -18,7 +18,6 @@ application: disabled_commands: - self:install - - self:update service: name: "Upsun (formerly Platform.sh)" diff --git a/internal/config/upsun-cli.yaml b/internal/config/upsun-cli.yaml index 19051f8aa..790ed3046 100644 --- a/internal/config/upsun-cli.yaml +++ b/internal/config/upsun-cli.yaml @@ -18,7 +18,6 @@ application: disabled_commands: - self:install - - self:update - local:build - local:drush-aliases - project:variable:delete diff --git a/internal/legacy/cert_store_windows_test.go b/internal/legacy/cert_store_windows_test.go index 141c28dd0..7b6f80373 100644 --- a/internal/legacy/cert_store_windows_test.go +++ b/internal/legacy/cert_store_windows_test.go @@ -252,7 +252,6 @@ func openMachineRootStore(t *testing.T) windows.Handle { return store } - // withoutHanging fails the test if fn does not return in time. func withoutHanging(t *testing.T, description string, fn func() error) { t.Helper() diff --git a/legacy/.php-cs-fixer.dist.php b/legacy/.php-cs-fixer.dist.php index 1a6f0b55f..38babd508 100644 --- a/legacy/.php-cs-fixer.dist.php +++ b/legacy/.php-cs-fixer.dist.php @@ -4,7 +4,6 @@ ->in(__DIR__) ->notPath([ 'config/cache/container.php', // Ignore generated file - 'dist/installer.php', // Keep old PHP compatibility 'tests/data', // Ignore test data ]) ; diff --git a/legacy/.platform.app.yaml b/legacy/.platform.app.yaml deleted file mode 100644 index 6b87b4d4e..000000000 --- a/legacy/.platform.app.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: app -type: php:8.2 - -dependencies: - php: - composer/composer: "^2" - -hooks: - build: | - set -e - mkdir -p web - - export version=dev-"$PLATFORM_TREE_ID" - - composer install -a --no-dev --no-interaction - - composer install -d vendor-bin/box --no-interaction - - mkdir -p vendor/bin - ln -s "$(realpath vendor-bin/box/vendor/bin/box)" vendor/bin/box - - ./bin/platform self:build --no-composer-rebuild --yes --replace-version "$version" --output web/platform.phar - - cat << EOF > .environment - alias platform="php \${PLATFORM_APP_DIR}/web/platform.phar" - export CLI_URL_PATH=platform.phar - export CLI_BUILD_DATE="$(date)" - export CLI_VERSION="$version" - EOF - - mv dist/dev-build-index.php web/index.php - mv dist/installer.php web/installer - export sha256="$(sha256sum web/platform.phar | cut -f1 -d' ')" - cat < web/manifest.json - [ - { - "version": "$version", - "sha256": "$sha256", - "name": "platform.phar", - "url": "platform.phar" - } - ] - EOF - -web: - locations: - '/': - root: web - index: ['index.php'] diff --git a/legacy/README.md b/legacy/README.md index 72ccdbe6b..30ece5922 100644 --- a/legacy/README.md +++ b/legacy/README.md @@ -23,20 +23,6 @@ scoop install platform For manual installation, you can also [download the latest binaries](https://github.com/platformsh/cli/releases/latest). -### Legacy installer - -_This installation method is considered legacy and is discouraged, use one of the methods above instead. Starting with version 5.x, this installation method will not be supported._ - -In order to use the Legacy installer, you need to have an operating system supported by PHP (Linux, OS X, or Windows) and PHP 8.2 or higher, with the following extensions: `curl`, `json`, `pcre`, and `phar`. - -Run this command to install the CLI using the legacy installer, given that you have PHP already installed: - -```console -curl -sS https://platform.sh/cli/installer | php -``` - -In some Windows terminals you may need `php.exe` instead of `php`. - ## Upgrade Upgrade using the same tool: @@ -94,7 +80,6 @@ Other customization is available via environment variables, including: * `PLATFORMSH_CLI_SESSION_ID`: change user session (default 'default'). The `session:switch` command (beta) is now available as an alternative. * `PLATFORMSH_CLI_SHELL_CONFIG_FILE`: specify the shell configuration file that the installer should write to (as an absolute path). If not set, a file such as `~/.bashrc` will be chosen automatically. Set this to an empty string to disable writing to a shell config file. * `PLATFORMSH_CLI_TOKEN`: an API token. *_Warning_*: An API token can act as the account that created it, with no restrictions. Use a separate machine account to limit the token's access. Additionally, storing a secret in an environment variable can be insecure. It may be better to use the `auth:api-token-login` command. The environment variable is preferable on CI systems like Jenkins and GitLab. -* `PLATFORMSH_CLI_UPDATES_CHECK`: set to 0 to disable the automatic updates check * `PLATFORMSH_CLI_SSH_AUTO_LOAD_CERT`: set to 0 to disable automatic loading of an SSH certificate when running login or SSH commands * `PLATFORMSH_CLI_REPORT_DEPRECATIONS`: set to 1 to enable PHP deprecation notices (suppressed by default). They will only be displayed in debug mode (`-vvv`). * `CLICOLOR_FORCE`: set to 1 or 0 to force colorized output on or off, respectively diff --git a/legacy/composer.json b/legacy/composer.json index 64330152b..5fc0c4ec1 100644 --- a/legacy/composer.json +++ b/legacy/composer.json @@ -14,7 +14,6 @@ "symfony/filesystem": "^7", "symfony/process": "^7", "symfony/event-dispatcher": "^7", - "padraic/phar-updater": "^1.0", "symfony/dependency-injection": "^7", "symfony/config": "^7", "ext-json": "*", @@ -22,17 +21,10 @@ "khill/php-duration": "^1.1", "symfony/polyfill-mbstring": "^1.19", "symfony/polyfill-iconv": "^1.19", - "padraic/humbug_get_contents": "dev-allow-php-8 as 1.1.3", "platformsh/oauth2": "^1@beta", "giggsey/libphonenumber-for-php-lite": "^8.13", "symfony/var-dumper": "^7.3" }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/pjcdawkins/humbug_get_contents" - } - ], "suggest": { "drush/drush": "For Drupal projects" }, diff --git a/legacy/composer.lock b/legacy/composer.lock index 11b1cdfd3..44f2f467c 100644 --- a/legacy/composer.lock +++ b/legacy/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "629cf68cfb23c5ae914e92dff124c397", + "content-hash": "363e7e9f7b7eecf9b0943dbe9b5f515a", "packages": [ { "name": "cocur/slugify", @@ -788,139 +788,6 @@ }, "time": "2025-11-25T22:17:17+00:00" }, - { - "name": "padraic/humbug_get_contents", - "version": "dev-allow-php-8", - "source": { - "type": "git", - "url": "https://github.com/pjcdawkins/humbug_get_contents.git", - "reference": "47a1fe74c6bf9d409c443c07a7698b7ae003abc2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/pjcdawkins/humbug_get_contents/zipball/47a1fe74c6bf9d409c443c07a7698b7ae003abc2", - "reference": "47a1fe74c6bf9d409c443c07a7698b7ae003abc2", - "shasum": "" - }, - "require": { - "composer/ca-bundle": "^1.0", - "ext-openssl": "*", - "php": "^5.3 || ^7.0 || ^7.1 || ^7.2 || ^8.2" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.1", - "mikey179/vfsstream": "^1.6", - "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": false - }, - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "psr-4": { - "Humbug\\": "src/" - }, - "files": [ - "src/function.php", - "src/functions.php" - ] - }, - "autoload-dev": { - "psr-4": { - "Humbug\\Test\\": "tests/Humbug/Test/" - } - }, - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Pádraic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" - }, - { - "name": "Théo Fidry", - "email": "theo.fidry@gmail.com" - } - ], - "description": "Secure wrapper for accessing HTTPS resources with file_get_contents for PHP 5.3+", - "homepage": "https://github.com/padraic/file_get_contents", - "keywords": [ - "download", - "file_get_contents", - "http", - "https", - "ssl", - "tls" - ], - "support": { - "source": "https://github.com/pjcdawkins/humbug_get_contents/tree/allow-php-8" - }, - "time": "2024-12-16T12:49:02+00:00" - }, - { - "name": "padraic/phar-updater", - "version": "v1.0.6", - "source": { - "type": "git", - "url": "https://github.com/humbug/phar-updater.git", - "reference": "d01d3b8f26e541ac9b9eeba1e18d005d852f7ff1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/humbug/phar-updater/zipball/d01d3b8f26e541ac9b9eeba1e18d005d852f7ff1", - "reference": "d01d3b8f26e541ac9b9eeba1e18d005d852f7ff1", - "shasum": "" - }, - "require": { - "padraic/humbug_get_contents": "^1.0", - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "Humbug\\SelfUpdate\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Pádraic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" - } - ], - "description": "A thing to make PHAR self-updating easy and secure.", - "keywords": [ - "humbug", - "phar", - "self-update", - "update" - ], - "support": { - "issues": "https://github.com/humbug/phar-updater/issues", - "source": "https://github.com/humbug/phar-updater/tree/1.0" - }, - "abandoned": true, - "time": "2018-03-30T12:52:15+00:00" - }, { "name": "platformsh/client", "version": "3.x-dev", @@ -6319,12 +6186,6 @@ } ], "aliases": [ - { - "package": "padraic/humbug_get_contents", - "version": "dev-allow-php-8", - "alias": "1.1.3", - "alias_normalized": "1.1.3.0" - }, { "package": "platformsh/client", "version": "3.9999999.9999999.9999999-dev", @@ -6334,7 +6195,6 @@ ], "minimum-stability": "stable", "stability-flags": { - "padraic/humbug_get_contents": 20, "platformsh/client": 20, "platformsh/console-form": 10, "platformsh/oauth2": 10 diff --git a/legacy/config-defaults.yaml b/legacy/config-defaults.yaml index 1aa190935..c4ce1e7a7 100644 --- a/legacy/config-defaults.yaml +++ b/legacy/config-defaults.yaml @@ -23,8 +23,8 @@ application: # Version number. # - # This placeholder is replaced by Box (via the self:build and self:release - # commands) with a manually specified or autogenerated version string. + # This placeholder is replaced by Box (via the self:build command) with a manually + # specified or autogenerated version string. version: '@version-placeholder@' # Environment variable prefix for overriding configuration. @@ -43,10 +43,6 @@ application: # Where application state is saved, under the {application.user_config_dir}. user_state_file: 'state.json' - # Config relating to the self:release command for making GitHub releases. - github_repo: null - release_branch: 'main' - # Whether to prompt to run the self:install command if it wasn't already. # # The prompt is never shown under the Go wrapper, nor if multiple CLI @@ -85,10 +81,6 @@ application: # The Composer package name (for legacy PHP Composer installations). package_name: null - # Legacy installer and manifest locations. - installer_url: null - manifest_url: null - # Enable auto-creating Drush aliases for Drupal environments. drush_aliases: true @@ -389,15 +381,6 @@ pagination: # Items per page. Can be overridden with --count. count: 20 -# Automatic updates. -# This can be overridden in the user config file. -updates: - # The interval between automatic update checks (in seconds). 604800 = 7 days. - check_interval: 604800 - - # Overridden by the {application.env_prefix}UPDATES_CHECK env var. - check: true - # Whether to enable migration to the new Go "wrapper" CLI. migrate: prompt: false diff --git a/legacy/config.yaml b/legacy/config.yaml index 8b9c5b9c2..80d7b3496 100644 --- a/legacy/config.yaml +++ b/legacy/config.yaml @@ -12,14 +12,10 @@ application: user_config_dir: '.platformsh' package_name: 'platformsh/cli' - installer_url: 'https://platform.sh/cli/installer' - manifest_url: 'https://platform.sh/cli/manifest.json' - github_repo: 'platformsh/legacy-cli' mark_unwrapped_legacy: true wrapped_disabled_commands: - self:install - - self:update service: name: 'Upsun (formerly Platform.sh)' diff --git a/legacy/config/services.yaml b/legacy/config/services.yaml index b65170702..cdf0e8edd 100644 --- a/legacy/config/services.yaml +++ b/legacy/config/services.yaml @@ -28,8 +28,6 @@ services: public: true Platformsh\Cli\Service\SelfInstallChecker: public: true - Platformsh\Cli\Service\SelfUpdateChecker: - public: true # Configure the cache service, which is created via a factory. Doctrine\Common\Cache\CacheProvider: diff --git a/legacy/dist/README.md b/legacy/dist/README.md deleted file mode 100644 index 1264827e1..000000000 --- a/legacy/dist/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 'dist' directory - -This directory contains installer file(s) for the CLI. - -These files are not packaged nor used directly by the CLI. Instead, they are -copied to the main Platform.sh website under these URLs: - -``` -https://platform.sh/cli/installer -https://platform.sh/cli/manifest.json -``` diff --git a/legacy/dist/dev-build-index.php b/legacy/dist/dev-build-index.php deleted file mode 100644 index 62e331bf9..000000000 --- a/legacy/dist/dev-build-index.php +++ /dev/null @@ -1,164 +0,0 @@ -getStr('application.name'); -$envPrefix = $config->getStr('service.env_prefix'); -$branch = (string) getenv($envPrefix . 'BRANCH', true); -$treeId = (string) getenv($envPrefix . 'TREE_ID', true); - -$pharUrl = getenv('CLI_URL_PATH', true) ?: 'platform.phar'; -$pharHash = hash_file('sha256', __DIR__ . '/' . ltrim((string) getenv('CLI_URL_PATH', true), '/')); -if ($timestamp = getenv('CLI_BUILD_DATE', true)) { - $pharDate = date('c', is_numeric($timestamp) ? (int) $timestamp : (int) strtotime($timestamp)); -} else { - $pharDate = false; -} - -if ($config->has('application.github_repo')) { - $sourceLink = 'https://github.com/' . $config->getStr('application.github_repo'); - $sourceLinkSpecific = $sourceLink; - if ($branch) { - if (str_starts_with($branch, 'pr-') && is_numeric(substr($branch, 3))) { - $sourceLinkSpecific .= '/pull/' . substr($branch, 3); - } else { - $sourceLinkSpecific .= '/tree/' . rawurlencode($branch); - } - } -} else { - $sourceLink = false; - $sourceLinkSpecific = false; -} - -$baseUrl = 'https://' . $_SERVER['HTTP_HOST']; -$installScript = sprintf( - 'curl -sfS %s | php -- --dev --manifest %s', - $baseUrl . '/installer', - $baseUrl . '/manifest.json', -); - -$revertScript = ''; -if ($config->has('application.installer_url')) { - $revertScript = sprintf( - 'curl -sfS %s | php', - $config->getStr('application.installer_url'), - ); -} - -?> - - - - - <?= htmlspecialchars($appName) ?> | dev build - - - - - - -

- -

- -

Development build

- -

- Download: -

- - -

- Build date: -

- - -

- SHA-256 hash: -

- - -

- Tree ID: -

- - -

- Branch: -

- - -

- Source: -

- - -

Testing instructions

-

- Install this version with:
- -

- -

- Install the stable version again with:
- -

- - - - diff --git a/legacy/dist/installer.php b/legacy/dist/installer.php deleted file mode 100644 index 2776eb227..000000000 --- a/legacy/dist/installer.php +++ /dev/null @@ -1,1057 +0,0 @@ -run(); -} - -class Installer -{ - private $envPrefix; - private $manifestUrl; - private $configDir; - private $executable; - private $cliName; - private $userAgent; - private $pharName; - private $argv; - private $serviceEnvPrefix; - private $migratePrompt = false; - private $migrateDocsUrl; - - public function __construct(array $args = []) - { - $this->argv = !empty($args) ? $args : $GLOBALS['argv']; - - // This config is automatically replaced by the self:build command, - // to match the values in config.yaml. - $config = /* START_CONFIG */array ( - 'envPrefix' => 'PLATFORMSH_CLI_', - 'manifestUrl' => 'https://platform.sh/cli/manifest.json', - 'configDir' => '.platformsh', - 'executable' => 'platform', - 'cliName' => 'Upsun CLI (Platform.sh compatibility)', - 'userAgent' => 'platformsh-cli', - 'serviceEnvPrefix' => 'PLATFORM_', - 'migratePrompt' => true, - 'migrateDocsUrl' => 'https://docs.upsun.com/anchors/fixed/cli/', -)/* END_CONFIG */; - - $required = ['envPrefix', 'manifestUrl', 'configDir', 'executable', 'cliName']; - if ($missing = \array_diff($required, \array_keys($config))) { - throw new \InvalidArgumentException('Missing required config key(s): ' . \implode(', ', $missing)); - } - - foreach ($config as $key => $value) { - if (\property_exists($this, $key)) { - $this->{$key} = $value; - } - } - - if (getenv($this->envPrefix . 'MANIFEST_URL') !== false) { - $this->manifestUrl = getenv($this->envPrefix . 'MANIFEST_URL'); - } elseif ($manifestOption = $this->getOption('manifest')) { - $this->manifestUrl = $manifestOption; - } - - $this->userAgent = sprintf( - '%s-installer (%s; %s; PHP %s)', - $this->userAgent ?: 'cli', - php_uname('s'), - php_uname('r'), - PHP_VERSION - ); - $this->pharName = $this->executable . '.phar'; - - $this->ensureStreamConstants(); - } - - /** - * Runs the install itself. - */ - public function run() - { - error_reporting(E_ALL); - ini_set('log_errors', 0); - ini_set('display_errors', 1); - - $this->output($this->cliName . " installer", 'heading'); - - if ($this->migratePrompt && !$this->isCI() && !getenv($this->envPrefix . 'NO_LEGACY_WARNING') && !$this->flagEnabled('no-legacy-warning')) { - $this->output(''); - $this->output('Warning', 'heading'); - $this->output('This is the "legacy" PHP-based installer and is no longer recommended.'); - if (!empty($this->migrateDocsUrl)) { - $this->output('You can install the latest release for your operating system by following these instructions:'); - $this->output($this->migrateDocsUrl, 'info'); - } - $this->output(''); - $this->output('To suppress this message, set the environment variable ', null, false); - $this->output($this->envPrefix . 'NO_LEGACY_WARNING=1', 'info'); - if ($this->isInteractive() && $this->isTerminal(STDERR)) { - $this->output(''); - $waitTime = 10; - // Check STDIN in a loop to see if the user hit a key. - if ($this->isTerminal(STDIN) && stream_set_blocking(STDIN, false)) { - $start = microtime(true); - $this->output("Continuing with the installation in $waitTime seconds. Press Enter to continue now, or Ctrl+C to quit."); - while (microtime(true) - $start < $waitTime) { - if (readline() !== false) { - break; - } - usleep(300000); - } - } else { - $this->output("Continuing with the installation in $waitTime seconds (use Ctrl+C to quit)..."); - sleep($waitTime); - } - } - } - - // Run environment checks. - $this->output(PHP_EOL . "Environment check", 'heading'); - - // Check that the JSON and Phar extensions are installed (needed in this script). - $this->checkExtension('json'); - $this->checkExtension('phar'); - - // Check that Git is installed. - $this->check( - 'Git is installed.', - 'Warning: Git will be needed.', - function () { - if (defined('PHP_WINDOWS_VERSION_BUILD')) { - $command = 'where git'; - } else { - $command = 'command -v git'; - } - exec($command, $output, $return_var); - return $return_var === 0; - }, - false - ); - - // Check other required extensions. - $this->checkExtension('openssl'); - $this->checkExtension('pcre'); - - $this->check( - 'The "curl" PHP extension is installed.', - 'The "curl" PHP extension is strongly recommended.', - function () { - return extension_loaded('curl'); - }, - false - ); - - // Check pcntl and posix - needed for tunnel:open and server:start. - // Skip the check on Windows, as they are not available there anyway. - if (DIRECTORY_SEPARATOR !== '\\') { - $this->check( - 'The "pcntl" and "posix" extensions are installed.', - 'The "pcntl" and "posix" extensions are needed for some commands.', - function () { - return extension_loaded('pcntl') && extension_loaded('posix'); - }, - false - ); - } - - // Check Suhosin restrictions. - if (extension_loaded('suhosin')) { - $this->check( - 'The "phar" stream wrapper is allowed by Suhosin.', - 'The "phar" stream wrapper is blocked by Suhosin.', - function () { - $allowed = ini_get('suhosin.executor.include.whitelist'); - $blocked = ini_get('suhosin.executor.include.blacklist'); - - if ((false === stripos($allowed, 'phar')) - || (false !== stripos($blocked, 'phar')) - ) { - return false; - } - - return true; - } - ); - } - - // Check whether PHP can open files via URLs. - $this->check( - 'The "allow_url_fopen" setting is on.', - 'The "allow_url_fopen" setting is off; it must be on.', - function () { - return (true == ini_get('allow_url_fopen')); - } - ); - - // Check a troublesome APC setting. - $this->check( - 'The "apc.enable_cli" setting is off.', - 'Warning: the "apc.enable_cli" is on; this may cause problems with Phar files.', - function () { - return (false == ini_get('apc.enable_cli')); - }, - false - ); - - // The necessary checks have passed. Start downloading the right version. - $this->output(PHP_EOL . 'Download', 'heading'); - - $latest = $this->performTask('Finding the latest version', function () { - return $this->findLatestVersion($this->manifestUrl); - }); - - $this->performTask('Downloading version ' . $latest['version'], function () use ($latest) { - $url = $latest['url']; - - // A relative download URL is treated as relative to the manifest URL. - if (strpos($url, '//') === false && strpos($this->manifestUrl, '//') !== false) { - $removePath = parse_url($this->manifestUrl, PHP_URL_PATH); - $url = str_replace($removePath, '/' . ltrim($url, '/'), $this->manifestUrl); - } - - $opts = $this->getStreamContextOpts(300); - $contents = \file_get_contents($this->getAuthenticatedRedirect($url), false, \stream_context_create($opts)); - if ($contents === false) { - return TaskResult::failure('The download failed'); - } - if (!file_put_contents($this->pharName, $contents)) { - return TaskResult::failure('Failed to write to file: ' . $this->pharName); - } - - return TaskResult::success(); - }); - - $pharPath = realpath($this->pharName) ?: $this->pharName; - - $this->performTask('Checking file integrity', function () use ($latest, $pharPath) { - if ($latest['sha256'] !== hash_file('sha256', $pharPath)) { - unlink($pharPath); - - return TaskResult::failure('The download was incomplete, or the file is corrupted'); - } - - return TaskResult::success(); - }); - - $this->performTask('Checking that the file is a valid Phar', function () use ($pharPath) { - try { - new \Phar($pharPath); - } catch (\Exception $e) { - return TaskResult::failure( - 'The file is not a valid Phar archive' . "\n" . $e->getMessage() - ); - } - - return TaskResult::success(); - }); - - $this->output(PHP_EOL . 'Install', 'heading'); - - $this->performTask('Making the Phar executable', function () use ($pharPath) { - if (!chmod($pharPath, 0755)) { - return TaskResult::failure('Failed to make the Phar executable'); - } - - return TaskResult::success(); - }); - - if ($homeDir = $this->getHomeDirectory()) { - $pharPath = $this->performTask('Moving the Phar to your home directory', function () use ($pharPath, $homeDir) { - $binDir = $homeDir . DIRECTORY_SEPARATOR . $this->configDir . DIRECTORY_SEPARATOR . 'bin'; - if (!is_dir($binDir) && !mkdir($binDir, 0700, true)) { - return TaskResult::failure('Failed to create directory: ' . $binDir); - } - - $destination = $binDir . DIRECTORY_SEPARATOR . $this->executable; - if (!rename($pharPath, $destination)) { - return TaskResult::failure('Failed to move the Phar to: ' . $destination); - } - - return TaskResult::success($destination); - }); - $this->output(' Executable location: ' . $pharPath); - } - - $this->output(PHP_EOL . 'Running self:install command...' . PHP_EOL); - $result = $this->selfInstall($pharPath); - - exit($result); - } - - /** - * Checks if a required PHP extension is installed. - * - * This attempts to give configuration advice if the extension exists but - * is not yet enabled. - * - * @param string $extension - */ - private function checkExtension($extension) - { - if (\extension_loaded($extension)) { - $this->output(' [*] The "' . $extension . '" PHP extension is installed.', 'success'); - return; - } - $this->output(' [X] The ' . $extension . ' PHP extension is required.', 'error'); - $extFilename = DIRECTORY_SEPARATOR === '\\' ? 'php_' . $extension . '.dll' : $extension . '.so'; - $extDirs = [ - PHP_EXTENSION_DIR, - dirname(PHP_BINARY) . DIRECTORY_SEPARATOR . 'ext', - ]; - foreach ($extDirs as $dir) { - $extPath = $dir . DIRECTORY_SEPARATOR . $extFilename; - if (!\file_exists($extPath)) { - continue; - } - $this->output("The extension already exists at: $extPath"); - if (!empty(PHP_CONFIG_FILE_SCAN_DIR) && \is_dir(PHP_CONFIG_FILE_SCAN_DIR)) { - $this->output( - "\nTo enable it, create a file named: " . PHP_CONFIG_FILE_SCAN_DIR . DIRECTORY_SEPARATOR . "$extension.ini" - . "\ncontaining this line:" - . "\nextension=$extPath" - ); - } else { - $this->output( - "\nTo enable it, edit your php.ini configuration file and add the line:" - . "\nextension=$extPath" - ); - } - break; - } - exit(1); - } - - /** - * Runs the 'self:install' command. - * - * @param string $pharPath The path of the Phar executable. - * - * @return int The command's exit code. - */ - private function selfInstall($pharPath) - { - $command = 'php ' . escapeshellarg($pharPath) . ' self:install --yes'; - if ($shellType = $this->getOption('shell-type')) { - $command .= ' --shell-type ' . escapeshellarg($shellType); - } - putenv('CLICOLOR_FORCE=' . ($this->terminalSupportsAnsi() ? '1' : '0')); - - return $this->runCommand($command, true); - } - - /** - * Finds the latest version to download from the manifest. - * - * @param string $manifestUrl - * - * @return TaskResult - */ - private function findLatestVersion($manifestUrl) - { - $manifest = file_get_contents($manifestUrl, false, \stream_context_create($this->getStreamContextOpts(15))); - if ($manifest === false) { - return TaskResult::failure('Failed to download manifest file: ' . $manifestUrl); - } - $manifest = json_decode($manifest, true); - if ($manifest === null) { - return TaskResult::failure('Failed to decode manifest file: ' . $manifestUrl); - } - - $allowedSuffixes = ['stable']; - foreach (['beta', 'alpha', 'dev'] as $suffix) { - if ($this->flagEnabled($suffix)) { - $allowedSuffixes[] = $suffix; - } - } - $phpVersion = PHP_VERSION; - $resolver = new VersionResolver(); - $versions = $resolver->findInstallableVersions($manifest, $phpVersion, $allowedSuffixes); - if (empty($versions)) { - return TaskResult::failure($resolver->explainNoInstallableVersions($manifest, $phpVersion, $allowedSuffixes)); - } - try { - $latest = $resolver->findLatestVersion($versions, $this->getOption('min'), $this->getOption('max')); - } catch (\Exception $e) { - return TaskResult::failure($e->getMessage()); - } - - return TaskResult::success($latest); - } - - /** - * @param string $summaryText Description of the task. - * @param callable $task A function that returns a TaskResult. - * @param string $indent Whether to indent the summary & errors. - * - * @return mixed The result of the task, if any. - */ - private function performTask($summaryText, $task, $indent = ' ') - { - $this->output($indent . $summaryText . '...', null, false); - /** @var TaskResult $result */ - $result = $task(); - if (!$result->isSuccess()) { - $this->output(''); - if ($message = $result->getMessage()) { - $this->output('Error: ' . $message, 'error'); - } - exit(1); - } - $this->output(' done', 'success'); - - return $result->getData(); - } - - /* - * Sets up the STDIN, STDOUT and STDERR constants. - * - * Due to a PHP bug, these constants are not available when the PHP script - * is being read from stdin. - * - * See https://bugs.php.net/bug.php?id=43283 - */ - private function ensureStreamConstants() - { - if (!defined('STDIN')) { - define('STDIN', fopen('php://stdin', 'r')); - } - if (!defined('STDOUT')) { - define('STDOUT', fopen('php://stdout', 'w')); - } - if (!defined('STDERR')) { - define('STDERR', fopen('php://stderr', 'w')); - } - } - - /** - * Runs a shell command. - * - * @param string $cmd - * @param bool $forceStdout Whether to redirect all stderr output to stdout. - * - * @return int The command's exit code. - */ - private function runCommand($cmd, $forceStdout = false) - { - $process = proc_open($cmd, [STDIN, STDOUT, $forceStdout ? STDOUT : STDERR], $pipes); - - return proc_close($process); - } - - /** - * Checks a condition, outputs a message, and exits if failed. - * - * @param string $success The success message. - * @param string $failure The failure message. - * @param callable $condition The condition to check. - * @param bool $exit Whether to exit on failure. - */ - private function check($success, $failure, $condition, $exit = true) - { - if ($condition()) { - $this->output(' [*] ' . $success, 'success'); - } elseif (!$exit) { - $this->output(' [!] ' . $failure, 'warning'); - } else { - $this->output(' [X] ' . $failure, 'error'); - exit(1); - } - } - - /** - * Outputs formatted text. - * - * @param string $text - * @param string $color - * @param bool $newLine - */ - private function output($text, $color = null, $newLine = true) - { - static $styles = [ - 'success' => "\033[0;32m%s\033[0m", - 'error' => "\033[31;31m%s\033[0m", - 'info' => "\033[33m%s\033[39m", - 'warning' => "\033[33m%s\033[39m", - 'heading' => "\033[1;33m%s\033[22;39m", - ]; - - $format = '%s'; - - if (isset($styles[$color]) && $this->terminalSupportsAnsi()) { - $format = $styles[$color]; - } - - if ($newLine) { - $format .= PHP_EOL; - } - - printf($format, $text); - } - - /** - * Test if a flag is on the command line. - * - * @param string $flag A flag name (only letters, shortcuts not supported). - * - * @return bool - */ - private function flagEnabled($flag) - { - return in_array('--' . $flag, $this->argv, true); - } - - /** - * Get a command-line option that requires a value. - * - * @param string $name An option name (only letters, shortcuts not supported). - * - * @return string - */ - private function getOption($name) - { - foreach ($this->argv as $key => $arg) { - if (strpos($arg, '--' . $name . '=') === 0) { - return substr($arg, strlen('--' . $name . '=')); - } - $next = isset($this->argv[$key + 1]) && substr($this->argv[$key + 1], 0, 1) !== '-' - ? $this->argv[$key + 1] - : ''; - if ($arg === '--' . $name) { - if ($next === '') { - throw new \InvalidArgumentException('Option --' . $name . ' requires a value'); - } - return $next; - } - } - - return ''; - } - - /** - * Returns whether to use ANSI escape sequences. - * - * @return bool - */ - private function terminalSupportsAnsi() - { - static $ansi; - if (isset($ansi)) { - return $ansi; - } - - global $argv; - if (!empty($argv)) { - if ($this->flagEnabled('no-ansi')) { - return $ansi = false; - } elseif ($this->flagEnabled('ansi')) { - return $ansi = true; - } - } - - // On Windows, default to no ANSI, except in ANSICON and ConEmu. - // Everywhere else, default to ANSI if stdout is a terminal. - /** @noinspection PhpComposerExtensionStubsInspection */ - return $ansi = (DIRECTORY_SEPARATOR == '\\') - ? (false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI')) - : (function_exists('posix_isatty') && posix_isatty(1)); - } - - /** - * Finds the user's home directory. - * - * @return string|false - * The user's home directory as an absolute path, or false on failure. - */ - private function getHomeDirectory() - { - $vars = [$this->envPrefix . 'HOME', 'HOME', 'USERPROFILE']; - foreach ($vars as $var) { - if ($home = getenv($var)) { - return realpath($home) ?: $home; - } - } - if (!empty($_SERVER['HOMEDRIVE']) && !empty($_SERVER['HOMEPATH'])) { - return $_SERVER['HOMEDRIVE'] . $_SERVER['HOMEPATH']; - } - - return false; - } - - /** - * Constructs stream context options for downloading files. - * - * @param int $timeout - * - * @return array - */ - private function getStreamContextOpts($timeout) - { - $opts = [ - 'http' => [ - 'method' => 'GET', - 'follow_location' => 1, - 'timeout' => $timeout, - 'user_agent' => $this->userAgent, - ], - ]; - if ($proxy = $this->getProxy()) { - $opts['http']['proxy'] = str_replace(['http://', 'https://'], ['tcp://', 'ssl://'], $proxy); - } - if ($this->flagEnabled('insecure')) { - $opts['ssl']['verify_peer'] = false; - $opts['ssl']['verify_peer_name'] = false; - } elseif ($path = $this->getCaBundle()) { - if (\is_dir($path)) { - $opts['ssl']['capath'] = $path; - } else { - $opts['ssl']['cafile'] = $path; - } - } - - return $opts; - } - - /** - * Returns the path to the system CA bundle, if found. - * - * Adapted from composer/ca-bundle. - * @link https://github.com/composer/ca-bundle - * @see \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath() - * - * @return string|false - */ - private function getCaBundle() - { - static $path; - if (isset($path)) { - return $path; - } - - $caBundlePaths = []; - - $caBundlePaths[] = \getenv('SSL_CERT_FILE'); - $caBundlePaths[] = \getenv('SSL_CERT_DIR'); - - $caBundlePaths[] = \ini_get('openssl.cafile'); - $caBundlePaths[] = \ini_get('openssl.capath'); - - $otherLocations = [ - '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package) - '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package) - '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package) - '/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package) - '/usr/ssl/certs/ca-bundle.crt', // Cygwin - '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package - '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option) - '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat? - '/etc/ssl/cert.pem', // OpenBSD - '/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x - '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package - '/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package - ]; - - foreach ($otherLocations as $location) { - $otherLocations[] = \dirname($location); - } - - $caBundlePaths = \array_filter(\array_merge($caBundlePaths, $otherLocations)); - - foreach ($caBundlePaths as $candidate) { - if ($this->caPathUsable($candidate)) { - return $path = $candidate; - } - } - - return $path = false; - } - - /** - * Returns if a CA bundle path should be used. - * - * Adapted from composer/ca-bundle. - * @link https://github.com/composer/ca-bundle - * @see \Composer\CaBundle\CaBundle::caFileUsable() - * @see \Composer\CaBundle\CaBundle::caDirUsable() - * - * @param string $path - * - * @return bool - */ - private function caPathUsable($path) - { - if (!\is_readable($path)) { - return false; - } - if (\is_file($path)) { - // Avoid openssl_x509_parse() on old PHP versions (CVE-2013-6420). - if (\function_exists('\\openssl_x509_parse') && PHP_VERSION_ID >= 50600) { - $contents = \file_get_contents($path); - if (!$contents || \strlen($contents) === 0) { - return false; - } - $contents = \str_replace('TRUSTED CERTIFICATE', 'CERTIFICATE', $contents); - return $contents !== false && \openssl_x509_parse($contents); - } - return false; - } - if (\is_dir($path)) { - return (bool) \glob($path . '/*'); - } - return false; - } - - /** - * If possible, this converts a URL to an authenticated redirect. - * - * This only affects GitHub for now. - * - * @param string $url - * - * @return string - * An authenticated redirection URL, if possible. Otherwise the original URL is returned. - */ - private function getAuthenticatedRedirect($url) - { - if (\strpos($url, '//github.com') === false) { - return $url; - } - $headers = $this->authHeaders($url); - if (!$headers) { - return $url; - } - $opts = $this->getStreamContextOpts(300); - $opts['http']['header'] = implode("\r\n", $headers); - $opts['http']['follow_location'] = 0; - $opts['http']['ignore_errors'] = true; - \file_get_contents($url, false, \stream_context_create($opts)); - // Check for a 301 or 302 response. - $headers = isset($http_response_header) ? $http_response_header : []; - if (isset($headers[0]) && \strpos($headers[0], ' 30') !== false) { - foreach ($headers as $header) { - // Read the Location header. - if (\stripos($header, 'Location: ') === 0) { - return \trim(\substr($header, 10)); - } - } - } - return $url; - } - - /** - * Generates authentication headers based on the request URL. - * - * At the moment this just supports github.com. - * - * @param string $url - * - * @return string[] - */ - private function authHeaders($url) - { - $host = \parse_url($url, PHP_URL_HOST); - - if ($host === 'github.com') { - // Use the GITHUB_TOKEN in the environment, if available. - if ($token = \getenv('GITHUB_TOKEN')) { - return ['Authorization: token ' . $token]; - } - - // Use COMPOSER_AUTH and decode it. - // See https://getcomposer.org/doc/06-config.md#github-oauth - if (($composer_auth = \getenv('COMPOSER_AUTH')) - && ($json = \json_decode($composer_auth, true)) !== null - && !empty($json['github-oauth'][$host])) { - return ['Authorization: token ' . $json['github-oauth'][$host]]; - } - - // Read the local GitHub token from the project container. - // The token allows for higher rate limits but is otherwise unprivileged. - if (\getenv('HOME') !== false) { - $authFilename = \getenv('HOME') . '/.global/auth.json'; - if (\is_readable($authFilename) - && ($contents = \file_get_contents($authFilename)) !== false - && ($json = \json_decode($contents, true)) !== null - && !empty($json['github-oauth'][$host])) { - return ['Authorization: token ' . $json['github-oauth'][$host]]; - } - } - } - return []; - } - - /** - * Finds a proxy address based on the https_proxy or http_proxy environment variable. - * - * @return string|null - */ - private function getProxy() - { - // The proxy variables should be ignored in a non-CLI context. - // This check has probably already been run, but it's important. - if (PHP_SAPI !== 'cli') { - return null; - } - foreach (['https', 'http'] as $scheme) { - if ($proxy = getenv($scheme . '_proxy')) { - return $proxy; - } - } - return null; - } - - /** - * Returns whether interactivity is possible and not disabled. - * - * @return bool - */ - private function isInteractive() - { - return !getenv($this->envPrefix . 'NO_INTERACTION') && !$this->flagEnabled('no-interaction'); - } - - /** - * Detects if running in a TTY terminal. - * - * @see \Platformsh\Cli\Service\Io::isTerminal() - * - * @param resource|int $descriptor - * - * @return bool - */ - private function isTerminal($descriptor) - { - return !function_exists('posix_isatty') || posix_isatty($descriptor); - } - - /** - * Detects a Platform.sh non-terminal Dash environment; i.e. a hook. - * - * @see \Platformsh\Cli\Command\CommandBase::detectRunningInHook() - * - * @return bool - */ - private function detectRunningInHook() - { - $envPrefix = $this->serviceEnvPrefix; - return getenv($envPrefix . 'PROJECT') - && basename(getenv('SHELL')) === 'dash' - && !$this->isTerminal(STDIN); - } - - /** - * Detects if running within a CI or local container system. - * - * @see \Platformsh\Cli\Command\CommandBase::isCI() - * - * @return bool - */ - private function isCI() - { - return getenv('CI') !== false // GitHub Actions, Travis CI, CircleCI, Cirrus CI, GitLab CI, AppVeyor, CodeShip, dsari - || getenv('BUILD_NUMBER') !== false // Jenkins, TeamCity - || getenv('RUN_ID') !== false // TaskCluster, dsari - || getenv('LANDO_INFO') !== false // Lando (https://docs.lando.dev/guides/lando-info.html) - || getenv('IS_DDEV_PROJECT') === 'true' // DDEV (https://ddev.readthedocs.io/en/latest/users/extend/custom-commands/#environment-variables-provided) - || $this->detectRunningInHook(); // PSH - } -} - -class TaskResult -{ - private $success = false; - private $message = ''; - private $data; - - private function __construct($success, $message = '', $data = null) - { - $this->success = $success; - $this->message = $message; - $this->data = $data; - } - - public static function success($data = null) - { - return new self(true, '', $data); - } - - public static function failure($errorMessage) - { - return new self(false, $errorMessage); - } - - public function isSuccess() - { - return $this->success; - } - - public function getMessage() - { - return $this->message; - } - - public function getData() - { - return $this->data; - } -} - -class VersionResolver -{ - /** - * Finds the latest installable version in the manifest. - * - * @param array $versions - * @param string $phpVersion - * @param array $allowedSuffixes - * - * @return array - * A list of versions, filtered by those that are installable. - */ - public function findInstallableVersions(array $versions, $phpVersion = PHP_VERSION, array $allowedSuffixes = ['stable']) - { - $installable = []; - foreach ($versions as $version) { - if (isset($version['php']['min']) && version_compare($version['php']['min'], $phpVersion, '>')) { - continue; - } - if ($dashPos = strpos($version['version'], '-')) { - $suffix = substr($version['version'], $dashPos + 1); - if (!in_array($suffix, $allowedSuffixes) && !in_array('dev', $allowedSuffixes)) { - continue; - } - } - $installable[] = $version; - } - - return $installable; - } - - /** - * Explains why no installable versions could be found. - * - * @param array $versions - * @param string $phpVersion - * @param array $allowedSuffixes - * - * @return string - */ - public function explainNoInstallableVersions(array $versions, $phpVersion = PHP_VERSION, array $allowedSuffixes = ['stable']) - { - $reasons = []; - foreach ($versions as $version) { - $name = 'v' . $version['version']; - if (isset($version['php']['min']) && version_compare($version['php']['min'], $phpVersion, '>')) { - $reasons[] = sprintf('Version %s requires PHP %s (current PHP version is %s)', $name, $version['php']['min'], $phpVersion); - continue; - } - if ($dashPos = strpos($version['version'], '-')) { - $suffix = substr($version['version'], $dashPos + 1); - if (!in_array($suffix, $allowedSuffixes) && !in_array('dev', $allowedSuffixes)) { - $reasons[] = sprintf('Version %s has the suffix -%s, not allowed', $name, $suffix); - continue; - } - } - } - - $explanation = 'No installable versions were found.'; - if (count($reasons)) { - foreach ($reasons as $reason) { - $explanation .= "\n - $reason"; - } - } - - return $explanation; - } - - /** - * Finds the latest version in a list of versions. - * - * @param array $versions - * @param string $min - * @param string $max - * - * @throws \RuntimeException - * - * @return array - */ - public function findLatestVersion(array $versions, $min = '', $max = '') - { - usort($versions, function (array $a, array $b) { - return version_compare($a['version'], $b['version']); - }); - $selected = []; - foreach ($versions as $version) { - $satisfiesMin = $min === '' || version_compare($version['version'], ltrim($min, 'v'), '>='); - $satisfiesMax = $max === '' || version_compare($version['version'], ltrim($max, 'v'), '<='); - if ($satisfiesMin && $satisfiesMax) { - $selected = $version; - } - } - if (empty($selected)) { - $message = 'Failed to find a version'; - if ($min !== '') { - $message .= ' >= ' . $min; - } - if ($max !== '') { - if ($min !== '') { - $message .= ' and'; - } - $message .= ' <= ' . $max; - } - throw new \RuntimeException($message); - } - - return $selected; - } -} diff --git a/legacy/dist/manifest.json b/legacy/dist/manifest.json deleted file mode 100644 index f0c6096d9..000000000 --- a/legacy/dist/manifest.json +++ /dev/null @@ -1,208 +0,0 @@ -[ - { - "version": "4.30.0", - "sha1": "3e442238f815ba68ce46fbdfd1454c3dd644fd26", - "sha256": "9903c7111a1b7b0c94a44a7e5a421c3abb58c3ff53fc39079e9b896d0e93cf0c", - "name": "platform.phar", - "url": "https://github.com/platformsh/legacy-cli/releases/download/v4.30.0/platform.phar", - "php": { - "min": "5.5.9" - }, - "notes": { - "4.0.0": "This release helps users migrate to the new CLI, which is now recommended.\n\nTo install the latest version see:\nhttps://docs.platform.sh/administration/cli.html\n\nThe new CLI is entirely backwards compatible with the \"Legacy\" CLI: features\nand usage are the same. However, it has a new installation method which no\nlonger requires PHP; it embeds the Legacy CLI and a PHP runtime as part of the\nsame binary.\n\nThe Legacy CLI will now prompt to install the new one, when appropriate. A\nnotice will be displayed if both CLIs are installed, with a suggestion to\ndelete the Legacy one.\n\nThe new CLI's version number will track the Legacy version that it wraps; in\nother words version numbers will be synchronized.\n\nOther changes:\n\n* Update ssh-key commands:\n - Use `ed25519` instead of `rsa` by default.\n - Recommend SSH certificates instead of keys.\n - Otherwise, use ed25519 instead of rsa by default.\n - Support interactive selection in ssh-key:delete.\n* Allow the `disk` command to be used during crons/deployments.\n* Always print the selected project/environment in verbose mode.\n* Fix wildcard matching for `environment:delete`.\n* Support wildcards when filtering by activity type.\n* Describe which columns are available in help for the `--columns` option\n (affects commands that list data).\n* Re-fetch the activity log to ensure log items are not missed.\n* Fix \"Cannot cast a filtered stream\" error (on PHP 7.4+).\n* Reduce blank lines from shell commands in verbose mode.\n* Update library `giggsey/libphonenumber-for-php` from 8.13.1 to 8.13.3.", - "4.0.1": "* Exit the legacy CLI after deleting (during migration) to avoid Phar errors.\n* Clear the buffer when re-fetching the activity log.\n - Fixes a JSON decoding error (occurring mainly with longer and busier logs).\n* Parse the TZ environment variable according to the glibc format.\n - Fixes \"Notice: date_default_timezone_set(): Timezone ID 'UTC0' is invalid\".\n - Non-zero offsets are not supported.", - "4.0.2": "* Exclude unnecessary `libphonenumber` data from Phar build. \n The build size increased when adding this library in v3.87.0. \n The size is now reduced again by 15MB.\n* The library version has also been updated from 8.13.3 to 8.13.4.\n* Follow symlinks when checking for multiple CLI instances.\n* installer: wait during migration warning even when STDIN is not a tty.", - "4.1.0": "New features:\n\n* Add a `source-operations:list` (`source-ops`) command and improve validation\n in the `source-operations:run` command.\n\nOther changes:\n\n* Ask fewer questions in the `project:create` (`create`) and `variable:create`\n (`var:create`) forms. The following can be used on the command line but will\n no longer be asked interactively:\n - the --plan, --environments and --storage in the project:create command\n - --json, --sensitive, --enabled, --prefix, --inheritable, --visible-build, and\n --visible-runtime in the variable:create command\n* Provide more ways to suppress the legacy warning.\n Reads the following environment variables to suppress the warning about\n migrating to the new CLI installation method:\n - PLATFORMSH_CLI_NO_LEGACY_WARNING=1\n - IS_DDEV_PROJECT=true\n - LANDO_INFO=anything\n - (in addition to the other currently supported variables which are CI,\n BUILD_NUMBER and RUN_ID)\n* Fix help and list commands to properly display the executable name.\n* Fix `--refresh` option in the `auth:info` command (it had no effect).\n* Allow base URLs for GitHub integrations.\n* If the user owns 1 organization (and is a member of others), select it by\n default.\n* Fix \"RecursiveDirectoryIterator UnexpectedValueException\" from unreadable\n directories while detecting the local app.\n* Dependency updates:\n - Bump giggsey/libphonenumber-for-php from 8.13.4 to 8.13.5 (#1223)\n - Bump composer/ca-bundle from 1.3.4 to 1.3.5 (#1219)", - "4.1.1": "* Do not require `--base-url` for GitHub and GitLab integrations. \n N.B. with apologies for the potential compatibility break, this changes the\n option name `--base-url` to `--bitbucket-url` for creating or updating\n Bitbucket Server integrations, which was necessary to fix the bug with GitHub\n and GitLab.\n* Fix failure to refresh token in `create` command resulting in an eventual\n error message \"Property not found: status\".", - "4.1.2": "* Add iconv and mbstring polyfills to Composer dependencies. \n Fixes the `iconv()` function not being found during some commands, on systems\n without the `mbstring` PHP extension.", - "4.2.0": "* Add `org:user:projects` (`oups`) command to list projects per user.\n* Mark the current project's org in the `org:list` (`orgs`) command.\n* Auto-select the organization of the current project, for organization-related\n commands.\n* Quieten debug output:\n - Reduce blank lines in very-verbose/debug shell output.\n - Reduce debug message noise when finding the CA bundle.\n - Suppress PHP deprecation notices by default.\n* Explain the SSH error from the `project:get` (`get`) command.\n* Add more detail to `variable:create` help.\n* Bump giggsey/libphonenumber-for-php from 8.13.6 to 8.13.7 (#1239)", - "4.2.1": "* Fix config to enable the org:user:projects command", - "4.3.0": "Add support for non-production environment domains.\n\nThis feature is only available to Enterprise and Elite customers and is currently in beta.\nIf you're an Enterprise or Elite customer, contact support to become a beta tester.\nOtherwise, contact sales first to upgrade your plan.\nSee: https://docs.platform.sh/overview/get-support.html", - "4.4.0": "New features:\n\n* Add `backup:get` and `backup:delete` commands.\n\nOther changes:\n\n* Show Git progress output during the `push` command.\n* Add new or missing activity types (for `exclude` and wildcard matching):\n environment.merge-pr, environment.pause, environment.resume, maintenance.upgrade, project.clear_build_cache.\n* Fix indenting of command output with carriage returns\n (e.g. in the Git progress output of `get` and `push`).\n* Fix suggested commands for non-production domains in the `domains` command.\n* Invalidate the environment variable cache on redeployment. \n This means relationships data is more likely to be up-to-date.", - "4.4.1": "* Update client: fix unavailable \"#edit\" operation for environment type access.\n* Update `backup:restore` command `--target` filter for migrated regions.\n* Bump giggsey/libphonenumber-for-php from 8.13.7 to 8.13.8.\n* Add configuration option `api.write_user_ssh_config` to control whether\n `~/.ssh/config` will be modified, overridden by the environment variable\n `PLATFORMSH_CLI_API_WRITE_USER_SSH_CONFIG`. The possible values are:\n - unset (or YAML `null`) (the default): ask to create/update `~/.ssh/config`\n - truthy (\"1\" or YAML `true`): create/update automatically without asking\n - falsy (\"0\" or YAML `false`): don't create/update, display suggested config", - "4.5.0": "## Add new metrics commands: `metrics`, `cpu`, and `mem`\n\nThis adds \"beta\"-level support for:\n\n* Reading CPU and memory metrics (and all metrics at once in the `metrics`\n command).\n* Viewing metrics for all services at once.\n* Viewing temporary disk usage (as well as persistent).\n* A `metrics:curl` command to help debug the API for an environment's metrics\n (intended for internal use; hidden from the command list).\n\nIt also changes the output of the `disk` command, which could require updates to\nscripts for compatibility:\n\n* New **default** columns \"Service\" (service) and \"/tmp %\" (tmp_percent).\n* The command reports data for multiple services by default, if no --service\n (-s) option is given.\n* The --type and --service options can support selecting multiple services with\n a wildcard or providing multiple arguments.\n* If there are multiple data points per timestamp (if multiple services are\n displayed), the rows are separated with dashes.\n* Other columns relating to temporary disks are available (tmp_used, tmp_limit,\n etc.). See the command help for more information.\n* The default interval is now 1/5th of the default range instead of 1/10th. By\n default, the range is 10min, so the interval is now 2min instead of 1min.\n\n## Other changes\n\n* Add an `--update` option to `var:create` (update if the variable exists).\n* Fix validation of `--format` when used with `-h`.\n This also marks the `xml` help format as deprecated, and explains when the\n simplexml extension is not installed.\n* Changes to `-vv` and `-vvv` logging:\n - Only print exception traces in debug mode\n - Avoid printing the same exception message twice\n - Print Guzzle request and response summaries in very-verbose mode\n - Turn off cURL debugging entirely, as it is somewhat uncontrollable, and the\n debug output may be copied and pasted without realising the consequences\n (some headers contain private information, in particular Authorization).\n - Avoid ProgressMessage overwriting previous lines in very-verbose mode\n - Only print shell commands in very-verbose mode\n - Only print extra shell command info in debug mode\n - Prefix shell command info with # and HTTP requests with > or < symbols\n - Print a debug message when loading from cache.\n - Improve output of selected environment (avoid printing twice).\n* Fix path column in `apps` command.\n* Warn when verbose mode is enabled in `:curl` commands.\n* Preload `ConsoleTerminateEvent` before forking in `tunnel:open`.\n* Fix error in `tunnel:open` when tunnels are already open.\n* Show beta commands in the command list.\n* Prefer the production environment over the default branch, when selecting a\n default environment.\n* Support '*' as a wildcard character.\n Different characters are convenient in different shells depending whether they\n need quoting. A '*' needs to be quoted in ZSH but not in Bash.\n* Support wildcards for table columns.", - "4.6.0": "New features:\n\n* Support the `httplog` generic log forwarding integration (when available).\n* Add a `--tmp` option to the `disk` command to show temporary disk columns.\n* Add a `--pipe` option to the `apps`, `services` and `workers` commands,\n making it easier to obtain a list of service names.\n* Allow selecting an organization by project ID in `org` commands.\n\nOther changes:\n\n* Implement hidden aliases, options and namespaces in the `json` and `md`\n formats for the `list` command.\n* Fix `integration:activities:list` (`int:ac:list`) command table output.\n* Explain when no values are found after filtering in the `metrics` command.\n* Tweak the description of `--replace` in the `domain:add` command.", - "4.6.1": "* On integration:add set `\"prune_branches\": false` if `\"fetch_branches\": false`\n to work around a validation problem in the API.\n* Add aliases and examples to JSON-formatted help.\n* Format dates consistently in metrics command messages.\n* Report subcommands similarly to shell commands in `-vv` output.\n* Increase cache reuse in `repo` commands.\n* Set a default environment in the `variable:create` command.\n* Fix TypeError while detecting Drupal in projects containing `index.php`.\n* Improve PHP version detection for the local build warning.\n* Bump giggsey/libphonenumber-for-php from 8.13.11 to 8.13.12 (#1262)", - "4.7.0": "New features:\n\n* Add `env:pause` and `env:resume` commands. Development environments are\n automatically paused after two weeks of inactivity to save resources and\n reduce carbon emissions. These commands add some manual control.\n\nOther changes:\n\n* Make first part of activity type name optional in `--type` and\n `--exclude-type`, e.g. \"cron\" will match \"environment.cron\".\n* Warn when an activity `--exclude-type` value is not recognized.\n* Fix non-interactive use of `env:delete --inactive`.\n* In `env:delete`, group environments together for confirmation questions (based\n on their status), instead of asking per environment.\n* Improve login required help message to suit non-interactive use.\n* Fix session loading for a non-default file storage location.\n* Remove warning when app config not found during command loading.", - "4.7.1": "* Restore the check to prevent deleting parent environments. \n The check had been imprudently removed in `v4.7.0`. \n This adds an option `--allow-delete-parent` as a way to skip the check.\n* Improve singular/plural output formatting in `env:delete`.\n* Improve the check for the production environment in the `push` command.\n* Only check for updates if the containing directory is writable (for legacy\n only-Phar installations).\n* Dependency updates:\n - Bump giggsey/libphonenumber-for-php from 8.13.12 to 8.13.15\n - Bump composer/ca-bundle from 1.3.5 to 1.3.6\n* SSH error handling improvements:\n - Avoiding recommending `ssh-keyscan` as it could be confusing.\n - Improve SSH error message when an environment is inactive.\n - Set timeout on test SSH connection.", - "4.7.2": "* Add support for checking whether ticket-based user verification is required.\n* Fix \"Incorrectly nested style tag found\" error. This was caused by bad\n formatting for the \"login required\" exception message when wrapped for\n narrower terminals.", - "4.8.0": "New features:\n\n* Support runtime operations:\n - Add an `op:list` (`ops`) command to list operations.\n - Add an `op:run` command to execute an operation.\n* Allow a dot (`.`) to select the default environment in the\n `--environment` (`-e`) option.\n* Config improvements:\n - Add more defaults for generic values.\n - Document defaults in the config.yaml file.\n - Support `CLI_CONFIG_FILE` to override the config (for internal use).\n* Custom (non-production) domains improvements:\n - Present an interactive choice of domain to attach.\n - Warn about attached domains on deletion.\n - Compatibility with project system version 22 (omit `type` parameter when\n creating a domain).\n - Remove beta messaging.\n - Change `--replace` to `--attach`.\n\nOther changes:\n\n* Improve printing of the selected project and environment.\n* Split list/array args by commas (not just the first arg).\n* Bump giggsey/libphonenumber-for-php from 8.13.15 to 8.13.17.\n* Add organization metadata in the `org:user:projects` command.\n* Improvements to the `project:get` (`get`) command:\n - Always display Git errors.\n - Allow the `build` command to be disabled.\n - Remove the recommendation to build after cloning.\n* Improve activity log handling:\n - Handle multiple activities on environment actions.\n - Prioritize displaying the log if there is one non-integration activity.\n - Display activity descriptions before waiting for them.\n - Improve elapsed time formatting.\n* Suggest exclusions in the `activity:list` command.\n* Add shortcuts for `--exclude-type` (`-x`) and `--type` (`-t`) to more\n activity commands.\n* Fix quoting of SSH arguments when multiple are given on the command line. \n For example the following command will now work:\n ```\n platform ssh -- sed -n '/04\\/Aug\\/2023\\:09\\:40/, /04\\/Aug\\/2023\\:09\\:45/p' /var/log/access.log\n ```\n* Improve Drupal app detection via composer packages.\n* Improvements to the `branch` command, including removing the `--force` option.\n* Recommend `project:create` from `project:list`.\n* Fix interactive selection of project-level variables.\n* Fix \"Link not found: #manage-variables\" when the user has read-only variables access.\n* Alias `project:set-remote` as `set-remote`\n* Allow paused environments to be deactivated.\n* Update the legacy region condition in `backup:restore`.", - "4.8.1": "* Fix quoting an ssh command when provided as a single arg", - "4.8.2": "* Fix activity:cancel state error message\n* Fix PHP < 7.3 compatibility (parse error for RuntimeOperation\\ListCommand.php)", - "4.8.3": "* Fix SSH command quoting on Windows.\r\n* Handle only one available plan in the `project:create` (`create`) command\r\n (use it as the default).\r\n* Fix message when no runtime operations are found in the `ops` command.\r\n* Remove extra newline in `env:delete` command output.\r\n* Bump giggsey/libphonenumber-for-php from 8.13.17 to 8.13.18 (#1302)", - "4.8.4": "Changes to internals:\n* Simplify configuration by introducing a defaults file\n* Allow any configuration to be overridden in the user-specific config file\n (though please note that local overrides can make updates more complex)\n* Remove old release note format from manifest.json\n\nAnd some minor bug fixes:\n* Fix type of http_build_query() argument in oauth-listener/index.php\n* Fix notice when additional sessions are active, 'Undefined array key \"session_id\"'", - "4.8.5": "* Fix \"Configuration not defined: application.tmp_sub_dir\" when the user config dir is not writable", - "4.8.6": "* Fix undefined BadResponseException class in variable:create command\n* Fix a PHP < 7.3 incompatibility in mount:size command\n* Fix TypeError in \"create\" command when fetching available regions", - "4.9.0": "New features:\n\n* Improvements to the `push` command:\n - Allow the current directory not to be mapped to a project - it need only be\n a Git repository.\n - Also allow pushing to a different project (other than the one mapped to the\n directory).\n - Add or change the Git remote if `--set-upstream` is given; recommend\n `set-remote` otherwise.\n - State what will happen and then ask for confirmation before pushing in all\n interactive cases (not just when pushing to production).\n - Use Git Push Options if available to activate the environment or set its\n parent (disabled by default).\n - Add a configuration option `detection.push_deploy_error_messages` which\n lists messages that indicate a deployment error, for example \"Error\n building project\" or \"Environment redeployment failed\". If one of these\n error messages is found in the Git push log, report it and return a\n non-zero (failure) exit code, `87`. Note this is only possible on the push\n that causes the initial deployment error; a subsequent push without a new\n commit would result in \"Everything up-to-date\" and a `0` (success) exit\n code.\n* Improve tunnel commands:\n - Add the 'url' as a property of each service in `tunnel:info`.\n - Deprecate table-related options that were unused in `tunnel:info`.\n - Add the tunnel 'url' as an optional table column in `tunnel:list`.\n* Add a `--bytes` (`-B`) option to the `metrics` command.\n\nOther changes:\n\n* Improvements to the `web` command:\n - Build the correct Console URL where relevant.\n - Allow for no environment to be selected.\n - Improve the command description.\n - Fix the error when no project is selected and no `service.accounts_url` is\n configured.\n* Update SSH certificate support:\n - Reduce the expiry buffer from 5 to 2 minutes.\n - Refresh the certificate after a failed connection if it is invalid.\n - Ensure the certificate matches necessary access token claims.\n - Fix extension decoding in `ssh-cert:info`.\n* Bump composer/ca-bundle from 1.3.6 to 1.3.7 (#1317)\n* Add two more activity types to the list\n* Add the environment commit to the local environment variables cache key\n - Lowers the risk of a stale cache for commands such as \"relationships\"\n - Removes the cache clear after the \"push\" command\n* Bump giggsey/libphonenumber-for-php from 8.13.18 to 8.13.19 (#1312)\n* Remove the minimum interval in metrics commands\n* Show command signature in help for \"You must specify a project\" error\n* Clarify 403 errors that may or may not be project-related\n* Filter by permissions (HAL link) when auto-selecting a project organization\n* Make the logo configurable for browser login\n* Use --include before --exclude in rsync commands (#1304)", - "4.10.0": "* Add sizing API support (when available) via `resources` commands. These\r\n commands are hidden by default, and shown if `api.sizing` is enabled.\r\n* Add an `api.projects_list_new` option - disabled by default - which uses a\r\n newer /users/{user_id}/extended-access API to list projects.\r\n* Use newer APIs for showing basic user account information.\r\n* Fix errors when `service.app_config_file` is not defined.\r\n* Bump giggsey/libphonenumber-for-php from 8.13.19 to 8.13.20 (#1319).", - "4.10.1": "* Fix detection of diskless services in `resources` commands.\n* Loosely validate profile sizes in `resources:set` command.\n (Allow `.5` to match `0.5`, `4` to match `4.0`, etc.)\n* Fix runtime operations error message.", - "4.10.2": "* Show simpler command names in help (usage and examples sections).\n* Fix `--host` option on the projects list, and replace it with `--region`.\n* Fixes relating to paused environments in two commands:\n - `push --activate`: when the environment is paused activation will not be\n attempted (it will automatically reactivate on deployment).\n - `environment:activate`: no error when the environment is paused. When\n possible it will now prompt for resuming the environment.\n* Filter the projects list by vendor, if configured.\n* Default to --set-remote in the `project:create` (`create`) command (if no\n remote project is already set).\n* Improve the plans default in the `project:create` (`create`) command. This\n removes the `service.available_plans` config option.", - "4.10.3": "* Avoid an exception if resources commands are used on an inactive environment\r\n* Bump giggsey/libphonenumber-for-php from 8.13.20 to 8.13.21 (#1329)", - "4.10.4": "* Fix failure to push to a \"dirty\" environment with `push --activate`\n* Confirm before changing a project in set-remote\n* Improve `project:create` (`create`) command:\n - Hide timeout options in help\n - Wait and retry while fetching the project\n - Recommend the `get` command afterwards\n - Avoid fetching `/regions` twice\n* Clarify when the SSO session expired\n* Add varnish to the list of \"diskless\" services for the `resources:set` command\n* Bump giggsey/libphonenumber-for-php from 8.13.21 to 8.13.22 (#1334)", - "4.11.0": "New features for Upsun projects (those using flexible resources):\r\n\r\n* Add `--resources-init` option for code source integrations (bitbucket,\r\n bitbucket_server, github, and gitlab).\r\n* Add `--resources-init` option to the `push` command when Git push options are\r\n enabled (they are currently disabled by default).\r\n* Show a link to resources docs in command help.\r\n* Show the container profile name when setting profile sizes.\r\n* Default to size 0.5, disk 512 MB when setting resources.\r\n* Validate against the service's minimum sizes.\r\n\r\nOther changes:\r\n\r\n* Lock while refreshing OAuth2 tokens or SSH certificates.\r\n This behavior can be disabled using the environment variable\r\n `{PREFIX}DISABLE_LOCKS` or the config entry `api.disable_locks`.\r\n* Filter the interactive choice of environments, to avoid suggesting\r\n environments that are in the wrong state.\r\n* Improve error message when the current deployment is not found, when this is\r\n caused by the environment being inactive.\r\n* Forward dynamic options when running a sub command.\r\n* Bump giggsey/libphonenumber-for-php from 8.13.22 to 8.13.26 (#1350)", - "4.11.1": "* Revert \"Forward dynamic options when running a sub command\"\n This reverts commit 1fcd5a91a1ea6d0f9a3e29dce4151e3d21c9a240.", - "4.11.2": "* Fix push command when push options are disabled", - "4.11.3": "* Respect resource limits, where flexible resources are used.\n\n This prevents adding resources that exceed the organization limit which could\n lead to a broken environment. It calculates if the requested update will\n exceed the current limit and if it does, it stops the operation. In case the\n user wants to proceed, they can use the `--force` argument.\n* Fix typo in reading --resources-init option.\n* Improve file locking on MacOS (using fsync or fflush).\n* Fix check for whether the SSO session expired - improves error message.", - "4.11.4": "* Fix: always attempt to choose a default environment when asking interactively (#1358)\n This should now apply again even when filtering is active, e.g. for the 'ssh'\r\n command.\n* Fix PHP <7.3 incompatibilities in ResourcesSetCommand", - "4.11.5": "* Enable `api.projects_list_new` by default.\n* Display the correct Console URL in the welcome command (when running\n `platform` or `upsun` on its own in a project directory).\n* Allow auto drush aliases to be turned off via a feature flag.\n These can be disabled via the `application.drush_aliases` config option or\n an env var: `export {PREFIX}_APPLICATION_DRUSH_ALIASES=0` (the `{PREFIX}`\n being `PLATFORMSH_CLI` or `UPSUN_CLI`).\n* Warn (no error) for invalid JSON when parsing the activity log.\n* Fix: invalid files being listed in \"log\" command.\n This ignores output from other processes during the SSH connection.\n* Fix: `var:create --update` not respecting the `--project` and `--no-wait`\n options when updating existing variables (thanks @romainneutron).\n* Fix: generating a cert twice during ssh-cert:load (via auto-login).\n* Fix: not activating environment with push options after asking interactively.", - "4.12.0": "New features:\n\n* Automatically install SSH host keys for public regions. \n If necessary you can disable this behavior with the environment (e.g.\n `export PLATFORMSH_CLI_SSH_HOST_KEYS_FILE=''`).\n* Only use the SSH certificate if available. \n Adds an `ssh.cert_only` config option which, if enabled, means only the\n certificate will be included in config, if a certificate exists, and not the\n user's \"default\" public key files (~/.ssh/id_rsa etc.). This is disabled by\n default. It can be enabled in the config file or in the environment (e.g.\n `export {PREFIX}_SSH_CERT_ONLY=1`).\n\nOther changes:\n\n* Bump composer/ca-bundle from 1.3.7 to 1.4.0\n* Use the new user-access API\n* Remove support for pre-\"Auth\" APIs\n* Remove unnecessary 'auth_api_enabled' and 'invitations' config flags\n* Update country/region names from CLDR\n* Remove unused available_plans config\n* Fix detection of SSO (vs normal) session expiry during refresh\n* SSH configuration changes:\n - Rename api.ssh_domain_wildcards to ssh.domain_wildcards\n - Rename api.auto_load_ssh_cert to ssh.auto_load_cert\n - Rename api.add_to_ssh_agent to ssh.add_to_agent\n - Rename api.write_user_ssh_config to ssh.write_user_config\n - Include the SSH certificate in config even if refresh fails", - "4.12.1": "* Bump giggsey/libphonenumber-for-php from 8.13.26 to 8.13.27\r\n* Update client: fix missing required parameter in ResourceWithReferences", - "4.13.0": "New features:\n\n* Add pagination to organization users commands (to support organizations with\n more than 50 members).\n* Add `--no-checkout` option; remove checkout warning in the `branch` command.\n* Make SSH options configurable, using the `ssh`.`options` YAML config key or\n the `{PREFIX}_SSH_OPTIONS` environment variable. The `RequestTTY force`\n option will no longer be sent by default in the `ssh` command. The\n `--identity-file` (`-i`) option for `ssh` and related commands will continue\n to work, but it is now hidden and deprecated.\n* Add timing columns for activity lists. For example, `--columns +time_execute`\n will display execution time in seconds alongside activities.\n\nOther changes:\n\n* SSH bugfixes:\n - Allow SSH to continue if writing configuration fails.\n - Omit `CertificateFile` if not supported by the OpenSSH version.\n - Do not rewrite config when running `ssh-cert:load --refresh-only`.\n - Fix the \"Expires at\" color in the `ssh-cert:load` command.\n - Avoid unnecessarily loading SSH keys.\n - Avoid passing `-t` to `ssh` when using `sql` with the `--raw` flag.\n* Un-beta `metrics` and runtime `ops` commands.\n* Deprecate `db:size` and `mount:size` commands.\n* Make `-c0` available consistently for showing all pages, in the commands\n which display paginated lists.\n* Warn during login when no refresh token is obtained.\n* Remove unnecessary `api_domain_suffix` config.\n* Skip interactive app/worker choice when there is only one app, and print\n the selected app/worker.\n* Avoid duplicate \"Selected environment\" verbose message when there is only one.\n* Remove unnecessary sort in backups list.\n* Update domain command help to clarify certificate files are for \"custom\" use\n (they are not required for SSL support).\n* Ignore a 404 from the current deployment in the `drush-aliases` command.", - "4.13.1": "* Fix a bug introduced in v4.12.0 which caused projects to be fetched via an\n old hostname, resulting in errors for projects that had been migrated\n between regions.", - "4.14.0": "New features:\n\n* Add team management commands (disabled by default until the feature is \n available)\n* Improve setting organization user permissions\n\nOther changes:\n\n* Bump giggsey/libphonenumber-for-php from 8.13.27 to 8.13.28\n* Display command name for listing projects without pagination\n* Update SSH host keys file\n* Improve help after errors by using Symfony exception more consistently", - "4.14.1": "* Reduce errors from SSH connections simultaneous with certificate refreshes:\r\n - Do not regenerate the SSH key pair every time.\r\n - Save the new key pair immediately after the new certificate.\r\n - Add a config parameter `ssh.cert_key_ttl`, and corresponding environment\r\n variable `PLATFORMSH_CLI_SSH_CERT_KEY_TTL`, defining the lifetime in\r\n seconds of a key pair. Set to 0 for the key pair never to expire or -1 for\r\n it to be regenerated every time the certificate is refreshed. So the\r\n previous behavior can be restored with:\r\n `export PLATFORMSH_CLI_SSH_CERT_KEY_TTL=-1`\r\n* Enable git push options by default.\r\n Restore the previous behavior with:\r\n `export PLATFORMSH_CLI_API_GIT_PUSH_OPTIONS=0`\r\n* Include more environments in filtered interactive choices: \"dirty\" (building)\r\n environments, and those with SSH URLs, as well as active environments.\r\n* Improve the error message if filtering returns no environments.\r\n* Fix type conflict in `resources` commands between separate `--app` options.\r\n* Bump giggsey/libphonenumber-for-php from 8.13.28 to 8.13.29 (#1395)\r\n* Clarify missing extensions error for `tunnel:open` on Windows.\r\n* Replace `/me` call with `/users/me` on login.\r\n* Print a debug message when refreshing the access token.\r\n* Remove use of legacy access API in `backup:create` command.\r\n* Fix exit code when failing to load the organization for a team.", - "4.14.2": "* Skip rerunning SSH cert check when SSHing from the CLI.\n* Highlight green regions in the project:create (create) command.\n* Support IPV6 ip address validation (#1405) (thanks @hanoii).\n* Set SSH log level to DEBUG3 when the CLI is in debug mode.\n* Fix metrics for multi-instance grid hosting.\n* Use the new backups API for restoring.", - "4.15.0": "Features relating to the sizing API (Upsun):\r\n\r\n* Support \"resources\" in the environment:sync (sync) command.\r\n* Add --resources-init option to five more commands:\r\n - `backup:restore`\r\n - `environment:activate`\r\n - `environment:branch` (`branch`)\r\n - `environment:merge` (`merge`)\r\n - `environment:synchronize` (`sync`)\r\n* At \"default\" and \"minimum\" as keywords for the resources:set command (use\r\n the newly exposed defaults in the API).\r\n\r\nOther changes:\r\n\r\n* Make `push --activate` ensure an environment is active, even when no changes\r\n were pushed.\r\n* Fix \"Failed to extract zip\" on Windows (by updating credential helpers).\r\n* Print PHP built-in warnings by default (without needing -v).\r\n* Avoid printing \"Determined CA bundle path\" more than once.\r\n* Bump giggsey/libphonenumber-for-php from 8.13.29 to 8.13.30.", - "4.15.1": "* Fix TypeError on pushing to a new target environment", - "4.15.2": "* Fix another type warning in push command for new environments\n* Remove undefined variable in activate command\n* Fix warning about strpos() after pushing", - "4.15.3": "* Fix support for the environment variable `{PREFIX}SSH_WRITE_USER_CONFIG`.\n* Skip automatic SSH auth when the host is external. This fixes an issue where \n the default SSH identity files (e.g. `~/.ssh/id_rsa`) were not being used for \n external hosts, e.g. GitHub (it affected those not running an SSH agent). The \n problem was introduced in v4.12.0 (commit 3b8bfbf4fde63d15c9079986e6bf9dbbc).\n* Improve SSH error reporting after authentication.\n* Revert \"Print PHP built-in warnings by default (without needing -v)\".\n* Hide the service image version.\n* Fix TypeError thrown when running `env:delete --no-delete-branch` on an \n inactive environment.", - "4.16.0": "New features:\r\n\r\n* Add a confirmation step before creating a backup. \r\n Non-interactive scripts must use `-y` or `--yes` or `--no-interaction`.\r\n For more detail, see: https://github.com/platformsh/legacy-cli/pull/1414\r\n* Use MariaDB-specific command names in \"db:\" commands.\r\n* Make `push --activate` ensure the environment status after pushing, whether or\r\n not new commits were pushed.\r\n\r\nOther changes:\r\n\r\n* Use git push options always.\r\n* Configuration changes:\r\n - Move config `service.header_prefix` to `detection.cluster_header` and make\r\n it optional.\r\n - Remove the need for a `service.slug` config key.\r\n* Fix recommending an SSH key when not needed.\r\n* Bump giggsey/libphonenumber-for-php from 8.13.30 to 8.13.31\r\n* Bump composer/ca-bundle from 1.4.0 to 1.4.1", - "4.17.0": "Significant changes:\n\n* Add `mfa_enabled` and `sso_enabled` columns to the `org:users` command.\n* In the `push` command, do not set a parent if none is specified. \n This also removes the interactive questions for the environment parent and \n type.\n* Remove `manual` value from `--resources-init` in 3 commands. \n Affects Upsun only, in commands: `activate`, `backup:restore`, and `branch`.\n\nSmaller changes:\n\n* Fix \"Variables cannot be bracketed\" error with SSH in fish.\n* Fix resolution of variable name during `variable:create --update`.\n* Fix obtaining GitHub token in the `self:release` command.\n* Dependency updates:\n - Bump composer/ca-bundle from 1.4.1 to 1.4.2 (#1424)\n - Bump phpseclib/phpseclib from 2.0.31 to 2.0.47 in /vendor-bin/box (#1422)\n - Bump giggsey/libphonenumber-for-php from 8.13.31 to 8.13.32 (#1425)", - "4.18.0": "New features:\n\n* Add --no-code option to backup:restore, and deprecate --restore-code\n\nOther changes:\n\n* Improvements to disk sizing in the resources:set command:\n - Use the API to validate whether a service can have a disk\n - Allow a disk size of 0\n* Fix \"No access token found\" after auto-login in auth:token command\n* Bump giggsey/libphonenumber-for-php from 8.13.32 to 8.13.34", - "4.18.1": "* Add `--type` filter to the `integration:list` command.\n* Fix deployment type detection in `metrics` commands.\n* Fix loading a subscription through the wrong org.\n* Avoid prompting for `self:install` when not the only instance.\n* Fix `--gateway-ports` option in tunnel commands.", - "4.19.0": "New features:\r\n\r\n* Add options for listing environments by status:\r\n - Add `--status` filter option to the `environment:list` command.\r\n - Add `--status`, `--only-status` and `--exclude-status` options to the\r\n `environment:delete` command.\r\n* Add commands to get and set build resources:\r\n - `resources:build:get` (or `build-resources:get`)\r\n - `resources:build:set` (or `build-resources:set`)\r\n\r\nOther changes:\r\n\r\n* Allow local OAuth listener to succeed on an alternative port.\r\n* Support RFC 9470 step-up authentication.\r\n* Disable wrapping on status and type columns in the environment list.\r\n* Remove YAML formatting when displaying floats.\r\n* Avoid misidentifying sessions from alternative configs.\r\n* Improve SSH/MFA error message.\r\n* Remove legacy region check in `backup:restore` command.\r\n* Bump giggsey/libphonenumber-for-php from 8.13.34 to 8.13.37.", - "4.19.1": "* Fix misleading \"More recent authentication is required\" message\n* Preserve left indent for all lines in a table", - "4.19.2": "* Improve handling of `push --activate` with a code source integration.\n* Avoid prompting to delete inactive branches when an integration with\n `prune_branches` is enabled.\n* Avoid an unnecessary fetch when an environment does not exist.\n* Avoid selecting an SSH key if a certificate exists.\n* Fix SSH error in fish shell: \"Missing argument at index 3\".\n* Expose the `integration:activities` alias in help.\n* Support SSH step-up authentication features.\n* Accommodate API tokens when showing step-up auth messages.\n* Disable the PHP memory limit (if any).\n* Bump giggsey/libphonenumber-for-php from 8.13.37 to 8.13.39.", - "4.19.3": "* Convert environment variable types when applying config overrides.\n* Refresh the SSH cert if the JTI differs (for smoother MFA/SSO).\n* Avoid saying the API token is invalid for other refresh errors (e.g. SSO \n expiry).\n* Avoid updating SSH session config twice in the same command.\n* Bump giggsey/libphonenumber-for-php from 8.13.39 to 8.13.40 (#1463)\n* Bump composer/ca-bundle from 1.4.2 to 1.4.3 (#1464)", - "4.20.0": "* Update backup:restore command resources options (Upsun):\n - Add a `--no-resources` option to avoid restoring resources from the backup.\n - Update the default for `--resources-init` to `backup` (previously `parent`).\n* Format the validation error message in the sync command.\n* Fix choice question autocomplete conflicting with index number.\n* Bump giggsey/libphonenumber-for-php from 8.13.40 to 8.13.42 (#1468)", - "4.20.1": "* Fix bug which flipped the stderr and stdout streams of subprocesses", - "4.20.2": "* Explain failing to activate the default branch when it has no code\n* Improvements to the `push` command:\n - Omit the parent and type from the git push options if they are not specified. \n This avoids an error when pushing to a non-default branch for the second \n time, when the default branch has no code.\n - Fix setting the type via git push options.", - "4.20.3": "* Fix 'The no-resources option does not exist.' in the backup:restore command.\n* Use stderr for showing current state in the resources:set command.", - "4.20.4": "* Add Unix paths for OpenSSH on Windows.\r\n If Windows-style paths are added to SSH configuration, this also adds a\r\n Unix-style path, as OpenSSH appears to have no support for Windows paths.\r\n Restore the previous behavior by setting the environment variable\r\n `PLATFORMSH_CLI_SSH_WINDOWS_PATHS` to `raw`.\r\n* Bump giggsey/libphonenumber-for-php from 8.13.42 to 8.13.45.\r\n* Extend the request timeout for /environments requests.\r\n* Fix invalid organization country code when picking a default.\r\n* Handle `EnvironmentStateException` when generating Drush aliases.\r\n* Auto-censor the Authorization header in `curl` commands.", - "4.20.5": "* Fix environments listing by ID", - "4.21.0": "New features:\n\n* Support listing teams for a single project.\n - The `team:list` (`teams`) command will now filter the teams list to those \n with access to the selected project, if any. The project is selected in the \n normal way, e.g. by `--project` (`-p`), or the current Git repository.\n - Use the `--all` option to list all the teams in the organization.\n - Add the `granted_at` column (`--columns +granted_at`) to see when the team \n was added to the project.\n\nOther changes:\n\n* Print necessary output in `--quiet` mode: \n Previously, the `--quiet` (`-q`) flag hid ALL output, on stderr and stdout. \n It now only hides message/error output (stderr), and continues to print \n necessary output (stdout).\n* Cache organization data locally (for up to 10 minutes by default).\n* Display the project's organization in the welcome command.\n* Silence output from `ssh-cert:load --refresh-only` (in non-verbose mode).\n* Add debug info for session storage.\n* Explain various actions being unavailable with a code source integration:\n synchronizing code, branching and merging.\n* Explain synchronizing being unavailable when the env or its parent is inactive.\n* Explain SSH unavailability when an env is paused.\n* Fix and improve project suspension warnings.\n* Improve formatting of Solr and PostgreSQL URLs.\n* Bump giggsey/libphonenumber-for-php from 8.13.45 to 8.13.46 (#1490)", - "4.21.1": "* Re-allow output from \"ssh-cert:load --refresh-only\", now that quiet mode works (#1491)\n This partially reverts 29a92850a2ce8a4f583e23bba75dcf1fdaad44c3\n* After branching, only set the upstream if the remote exists\n* Skip the cache when updating an org via the org:info command\n* Do not require SSH permission to list mounts\n* Handle old envs with outdated \"has_remote\" in the env:delete command", - "4.22.0": "New features:\n\n* Add `--init-repo` option for the `create` (`project:create`) command, as a \n shortcut for creating a project from code in a public repository.\n* Before creating a project, check the organization's permissions. This uses the \n `can-create` API endpoint, which supports more potential action prompts than \n the previous version (which only supported phone verification), e.g. to create \n a support ticket or to update the organization's billing details.\n\nOther changes:\n\n* Make `redis` and `ssh` args consistent; allow multiple args in the `redis` command.\n* Fix error after creating a new organization from a project directory.\n* Fix the project list cache not clearing automatically.", - "4.23.0": "New features:\n\n* Add Valkey support\n* Refresh the token and retry requests on a 401 error in :curl commands\n\nOther functional changes:\n\n* Rename \"web\" command to \"console\" (\"web\" remains as an alias)\n* Show \"(legacy)\" in the version output, if not wrapped.\n* Fix the console URL in the project:create (create) command\n* Fix duplication of commands in Markdown-formatted list\n* installer: fix use of GitHub credentials on container where available\n* Update composer/ca-bundle and drush/drush dependencies\n\nDevelopment-related changes:\n\n* Add Golang integration tests in `/go-tests`\n* Run tests and security checks in GitHub Actions\n* Use Composer 2 in dev builds\n* Remove composer-bin-plugin\n* Update countries script for new CLDR format", - "4.24.0": "New features:\n\n* Support guaranteed resources, adding a new CPU type concept (`shared` or\n `guaranteed`) to the `resources` commands.\n* Support manual deployments:\n - Add an `environment:deploy` command to deploy staged changes.\n - Add an `environment:deploy:type` command to view or change the deployment\n type (between `manual` and `automatic`).\n - Add a `deployment_type` property (read-only) to the `environment:info`\n command.\n - Support the `staged` activity state in activity-related commands.\n\nOther changes:\n\n* Increase the default limits for finding activities.\n* Stop bypassing the organization endpoint for subscriptions.\n* Update Go test dependencies.\n* Add `mark_unwrapped_legacy` config option (defaults to `false`).", - "4.25.0": "New features:\n\n* Autoscaling-related features:\n - Add an `autoscaling` command to read autoscaling settings.\n - Mark services with autoscaling in the `resources` command.\n - Disallow changing the instance count in `resources:set` when autoscaling is enabled.\n* Support the OpenTelemetry Protocol (`otlp`) integration, when available on the project.\n* Add a `--strategy` (`-s`) option to the `env:deploy` command (`stopstart` or `rolling`).\n* Require confirmation on `branch` or `env:activate` commands when guaranteed resources are configured.\n\nOther changes:\n\n* Update embedded docs links to the new permalink structure.\n* Avoid writing to stdout when opening a URL.\n* Treat `upsun` and `platformsh` vendors as equivalent in the project list from 2025-09-23.\n* Fix the command recommendation when there are staged activities.\n* Fix the Drush site URL when there is no app route (e.g. with Varnish).", - "4.26.0": "New features:\n\n* Add `autoscaling:set` command, to configure CPU-based autoscaling\n* Add support for organization types\n - Display the organization type in the `orgs` list\n - Add a `--type` filter in the `orgs` list\n - Add a `--type` option to `org:create`\n - Display the organization type in the `projects` list\n - Add an `--org-type` filter in the `projects` list\n* Add a `deploy` alias for the `env:deploy` command\n\nOther changes:\n\n* Fix \"This environment is inactive\" during activation, if the deployment cannot \n be fetched.", - "4.27.0": "* Support `memory` as a trigger for autoscaling\n* Set `--fail-with-body` by default in `:curl` commands\n* Update name of `otlp` integration to `otlplog`", - "4.28.0": "New features:\n\n* Display activities with non-zero exit codes as failed.\n* Allow specifying the deploy strategy on push.\n* Switch to new, more efficient endpoint for querying metrics. \n Output will be mostly the same as the previous `metrics` commands, with these\n exceptions:\n - Rows for the `router` service are now displayed by default.\n - The `---internal---storage` is no longer displayed by default.\n - `inodes` columns are now displayed by default in the `metrics` command. The\n first 5 table columns are the same, and the other commands' default columns\n are the same.\n - The `--interval` option is deprecated and no longer has an effect. The\n default interval displayed changes from 2 minutes to 1 minute.\n\nOther changes:\n\n* Error earlier in non-interactive mode when an organization ID is required.\n* Add an example for adding a column in activity:list command help.\n* Remove security contact from writable properties of billing profile.", - "4.28.2": "* Fix: the resources:build:get command should be hidden when sizing is disabled (#1583)", - "4.29.0": "* Allow configuring autoscaling for all services that support it.\n* Skip the \"type\" question when creating an organization.\n* Restore the metrics --interval option.\n* Return an error for a failed rolling deployment.", - "4.30.0": "* Add `--app-scope` option for variables.\n* Fix `PROCESS privilege(s)` error during `db:dump` of oracle-mysql databases.\n* Add `environment.alert` activity type." - } - }, - { - "name": "platform.phar", - "sha1": "f3d6cc436f176be78433ba18e6723d2ea48aac30", - "sha256": "ad9fb5bb88e6e984acad1688d255c773adc18b36b563715b6578280c7164126f", - "url": "https://github.com/platformsh/legacy-cli/releases/download/v3.88.1/platform.phar", - "version": "3.88.1", - "php": { - "min": "5.5.9" - }, - "notes": { - "3.0.0": "Major changes since version 2.x.x:\n\n* In version 2.x.x, the CLI had a project file structure including a 'repository' directory (and a 'builds' directory, 'shared', etc.). In version 3.x.x, this is no longer the case: the CLI operates directly inside your project's Git repository. The CLI will work with the old file structure, and there is a migration tool ('platform legacy-migrate') for updating to the new structure.\n\n* The web root has changed location from 'www' to '_www', to avoid conflicts with actual project files in the repo.\n\n* The CLI now requires PHP version 5.5.9 (minimum) due to updated dependencies.\n\nVersion 3.x.x also comes with many other smaller improvements and bug fixes.", - "3.8.0": "* Adds 'db:size' command for a rough estimate of DB disk usage\n* Fixes database commands where the relationship name is not 'database'\n* Fixes 'sql' command on PostgreSQL databases\n\nSee: https://github.com/platformsh/legacy-cli/releases/v3.8.0", - "3.9.0": "* Clears the local CLI caches after long-running activities complete\n* Allows users of the `platform sql` command to specify a relationship name as a command-line option\n* Adds commands in the `project:variable:` namespace for modifying project-level variables", - "3.10.0": "* Adds `environment:push` (`push`) command\n* Adds `--identity-file` option to commands using SSH\n* Adds `--parent` option to `environment:activate` command", - "3.11.0": "This release adds new options to the db:dump command:\n --gzip (-z) to compress the output\n --table to specify the table(s) to include\n --exclude-table to specify the table(s) to exclude\n --schema-only to include only schemas, not data\n\nAnd other minor bug fixes. See:\nhttps://github.com/platformsh/legacy-cli/releases/v3.11.0", - "3.12.0": "* Add `project:set-remote` command\n* Clarify self-update messages\n* Some minor bug fixes in the `branch`, `checkout`, and `integration:get` commands", - "3.12.1": "* Improve `get` command when the user does not have access to `master` branch\n* Allow `web` command to be used with a non-existent environment ID\n* Allow `updates.check` to be overridden by an env var, e.g. `PLATFORMSH_CLI_UPDATES_CHECK=0`\n* More specific suggested `.gitignore` file for Drupal vanilla projects", - "3.13.0": "* The `build` command now installs build-time dependencies by default (use\n `--no-deps` for the old behavior).\n* This release also adds commands to run a local web server (the PHP built-in\n server). But these commands are at a \"beta\" stability, and they can only be\n enabled via a feature flag, for example:\n\n # ---- ~/.platformsh/config.yaml ----\n experimental:\n # Enable \"platform create\" command.\n enable_create: true\n # Enable \"platform server:run\" and related commands.\n enable_local_server: true\n", - "3.14.0": "* Added `route:get` command to view a resolved route to an application\n* Renamed `environment:routes` command to `route:list` (still under the alias `routes`)\n* Improved caching for relationships, routes, and app config\n* Fixed integration option names", - "3.15.0": "This release adds `certificate` commands, to list, add, and delete SSL certificates from a project, including new automatically-provisioned Let's Encrypt certificates.\n\nOther changes since v3.14.5:\n\n* Fix an issue using `ssh` command on Windows\n* Make detection of the system timezone fallback to the PHP default\n* Use pseudo-terminal for SSH where possible (enables colors for some CLI programs)\n* Minor improvements to `activity:list`", - "3.16.0": "* Added a `--directory` option to `db:dump`\n* Added ability to load more than 10 activities in `activity:list`\n* Added `auth:token` and `project:curl` commands (intended for support and debugging)\n* Updated `self::install` so it doesn't write Bashisms to `.profile`\n* Updated activity descriptions\n* Switched to GitBook native search (rather than Google) in `docs` command", - "3.21.0": "* Added new `mount:upload` and `mount:download` commands, which help\n transferring files to and from writable file mounts, and a `mount:list`\n command which shows the available mounts. Thanks to @markushausammann for the\n PR.\n* Users may now log in with their username instead of their email address.\n* Fixed a bug introduced in v3.20.5, which unintentionally hid output from\n external commands.", - "3.22.0": "* Added automatic generation of YAML-format Drush site aliases for Drupal projects (in addition to the current PHP-format site aliases). Use `platform drush-aliases -r` to recreate aliases.\n* Moved site aliases from `~/.drush` to `~/.drush/site-aliases`.\n* Fixed the table of environments to stop wrapping environment IDs (it was breaking color formatting).\n* Changed wording of 'rebuild' warning, to 'redeploy'.", - "3.23.0": "- A public website URL can now be used in the --project (-p) option\n- Added `--exclude` and `--include` options to `mount` commands\n- Added `--excluded-environments` option for webhook integrations*\n- Added Gitlab integration support*\n\n(* the last two will apply to an upcoming Platform.sh version, 2.7)", - "3.24.0": "* Add repo:cat and repo:ls commands\n* Allow default target/source in mount:upload and mount:download when --yes is used\n* Add -W alias to --no-wait option in push command\n* (from Symfony Console 3.4) Allow case-insensitive command shortcuts\n* (from Symfony Console 3.4) Fix combining one-letter options\n* Write curl debug output to STDERR, not STDOUT\n* Fix some Drush 9.0.0-rc1 compatibility issues", - "3.25.0": "* Extends interactive question text in `integration:add` command, to clarify the meaning of options, their defaults, and their syntax\n* Adds support for the \"Enabled\" (`is_enabled`) flag on environment variables.\n* Adds `--no-expired` and `--only-expired` options to `certificate:list`\n* Improves the \"project not found\" message, to suggest other project IDs\n* Implements omitting non-required empty fields when adding an integration (allows earlier support for new integration options without breaking older projects)\n* Adds the SSH_AGENT_PID to auto inherited environment variables (fixes using an SSH agent to manage keys when used with the CLI's Git commands)\n* Adds an experimental `redis` command to connect to a Redis container (hidden from the command list for now)\n* Adds experimental OS X Keychain support (enabled via `experimental.use_keychain`)", - "3.26.0": "New features:\n* Support remote projects/environments in app:list command.\n* Add worker:list (workers) command.\n* Use \"deployments\" HTTP API (instead of SSH) in app:config command.\n* Auto-ensure webhooks on GitHub and GitLab integrations, where possible.\n\nOther changes:\n* Skip updates check if the Phar file is not writable.\n* Remove different coloration for local commands in `list` (there are so few listed).\n* Recommend --no-wait if snapshot:create is run in a non-TTY.\n* Suggest project:set-remote in RootNotFoundException.\n* Fix environment:drush (drush) command to use configured document root, or PLATFORM_DOCUMENT_ROOT where available.\n* Be stricter about when to use $_ENV, when getting parent variables for PHP <7.1.", - "3.27.0": "* Use browser login by default (the old login method is available as `auth:password-login`).\n* Set `--build-pull-requests-post-merge` to false by default (GitHub integrations).\n* Respect `--no-wait` in `environment:init`, and clear the environment cache afterwards.\n* In the `project:delete` command, leave access control to the API.\n* Support disabling or enabling colorized output via the CLICOLOR_FORCE environment variable.", - "3.28.0": "* Improved `activity:log` output to show more activity information.\n* Added `activity:get` command, hidden for now.\n* Added `--date-fmt` option to `activity:list` and `snapshot:list`.\n* Added detection for the date.timezone ini setting, and the TZ environment variable.\n* Fixed inverted requirement of -e/-a options in activity:log (`-a` should make `-e` not required).\n* Fixed user-defined aliases being prefixed with \"@\" (thanks to @GROwen, #677).\n* Avoid fatal error if invalid YAML config is encountered during updateDrushAliases().\n* Fixed SSH commands for very old OpenSSH versions <5.9 (using -t instead of RequestTTY).\n* Updated dependencies (mainly Symfony 3.4.2 -> 3.4.4).", - "3.29.0": "* [user:add] Improve `user:add` command to allow setting roles on all environments (aliased to `user:update`)\n* [user:get] Add `user:get` command (aliased to and deprecating `user:role`)\n* [db:dump] Remove --no-autocommit and simplify mysqldump args (#683)\n* Expand redeploy warning to recommend `vset`\n* [activity:get] Check for empty started_at when calculating duration\n* [redis] Recommend \"redis info\" command", - "3.30.0": "* Add `redeploy` command (available on projects with newer API versions).\n* [activity:list] Format activity descriptions where possible.\n* Print question text even when in automatic non-interactive mode.\n* Add --wait option to all commands, and emit a warning if nothing is specified\n when running inside a Platform.sh build/cron/deploy hook.\n* [integration:add] [integration:update] Ensure GitLab/GitHub hooks have the\n right events selected, and update them if not.\n* [self:install] Ensure directory exists when creating shell-config.rc\n* [activity:get] do not require an environment with --all and a partial ID\n* [login] Mention --force option, if running when already logged in.", - "3.31.0": "New features:\n\n* Always interact (where possible) to ask for the project or environment, if one\n isn't specified or detected.\n* Support new variable features:\n - Add `variable:create` and `variable:update` commands.\n - Allow creating 'sensitive' and 'non-inheritable' variables\n (`--sensitive=true` and `--inheritable=false`).\n - Add `variable:list` (`variables`) command.\n - These `variable:` commands now work with project- or environment-level\n variables. Specify the level with `--level` (`-l`). If you do not specify a\n level, you will be asked for one interactively where relevant.\n - The old commands still exist but are hidden from the command list.\n* Add `project:clear-build-cache` command\n* Enable `project:create` (`create`) command by default.\n* Add `--all` option to `list` command, to show hidden commands.\n* In `build` command, if --run-deploy-hooks option is used, run both \"deploy\"\n and \"post_deploy\" hooks.\n\nBug fixes:\n\n* Fix: boolean false fields omitted when creating integrations.\n* Fix wrapping of table cell contents that include decoration.\n\nOther changes:\n\n* Accept URLs (not just IDs) as project identifiers in `multi` command.\n* Un-hide some commands\n - Un-hide `activity:get` command.\n - Un-hide `worker:list` (`workers`)\n* Always use the deployment API for the app list (improves compatibility for\n multi-application Enterprise projects, in SSH-related commands).", - "3.31.3": "New features:\n\n* Add MongoDB commands: mongo, mongoexport, mongodump, and mongorestore.\n* In the user:add command, add a wildcard (%) for the environment ID, so you can\n add users to multiple environments concisely via the --role option.\n See: platform help user:add\n* Parse !include tags when loading a YAML file.\n\nOther changes:\n\n* In command help, word-wrap option descriptions to the terminal width.\n* Try to refresh an environment before giving an \"Operation not available\" error.\n* Fix `--pipe` option in the `user:role` command.\n* Suppress PHP's timezone warning while detecting the system timezone.", - "3.32.0": "* Increase the default interval between updates checks, from 1 day to 7 days.\n* Fix formatting for activity descriptions that contain a `/` character.\n* In the `project:set-remote` command, do not error out if the \"current\" project\n does not exist.\n* Minor performance improvement to `user` commands when looking up existing\n users.", - "3.33.0": "* Add `mount:size` command to find the disk usage of writable mounts.\n* Add `--sensitive` option for project-level variables (affects commands:\n `variable:create`, `variable:update`, and `variable:list`).\n* Clear environment/project caches after a 403 error.\n* Increase prominence of warning in `db:size` command.\n* Skip the automatic updates check if the Phar file is not writable.", - "3.34.0": "New features:\n\n* Add `service:list` (`services`) command.\n* Add --depth option to `project:get` (`get`) command (allow shallow clones).\n* Add --rebase option to the `environment:synchronize` (`sync`) command.\n* Add --prune-branches option for GitHub and GitLab integrations.\n* Add --worker option to the `environment:ssh` (`ssh`) command.\n\nOther changes:\n\n* When running pg_dump via the `db:dump` command, use the --no-owner flag.\n* Fix the `--all` option in the `list` command.\n* Prefer .bashrc over .bash_profile in the installer (self:install command).\n* Remove legacy API workaround in `integration:update`.\n* Update user commands for upstream API fix (improves performance).", - "3.35.0": "New features:\n\n* Add commit:list (commits) and commit:get commands for viewing commits via the\n API.\n* Add --commit (-c) option to repo:cat and repo:ls commands.\n* Add Bitbucket integration support.\n\nBug fixes:\n\n* Fix drush alias group name prepending bug. Custom aliases were having the\n alias group name prepended every time they were recreated, e.g. \"@foo.bar\"\n would become \"@foo.foo.bar\".\n* Allow changing the value of a project-level sensitive variable.\n\nOther changes:\n\n* Add explanations for \"Operation not available\" on environment actions.\n* Add additional help for the mount:size command.\n* Clean up --pipe output in the user:get command.\n* Show hidden service commands.", - "3.36.0": "* Fix a further source of 'Not logged in' errors immediately after login.\n* Add `integration:validate` command, and improve error reporting in other\n integration commands.\n* Deprecate the --type option in the `integration:update` command.\n* Fix alpha UI URLs for environments in the `web` command.", - "3.37.0": "New features:\n\n* Add --columns option for all tables (to filter and order table columns).\n* Add non-default \"disk\" and \"size\" columns to app:list command.\n* Add non-default \"updated_at\" column to domains:list command.\n* Add non-default \"host\" column to project:list command.\n* Allow loading more than 10 snapshots via --limit in snapshot:list command.\n\nOther changes:\n\n* Change CSV/TSV table output to make it cleaner. CSV/TSV cells will now only\n be enclosed in quote marks if necessary, conforming to RFC 4180.\n* Fail if password input cannot be hidden in auth:password-login command.\n* Set the remote only for new branches in environment:branch command.\n* Avoid wrapping dates in commit:list command.\n* Update list of allowed SSH key algorithms for ssh-key:add command.\n* Remove confirmation step in logout command.", - "3.38.0": "* Allow restoring snapshots to another environment in snapshot:restore command\n (with new --target and --branch-from options).\n* Sort interactive project/environment choices alphabetically.\n* Fix \"Name\"/\"name\" column should always have been \"Title\"/\"title\" in\n environment:list command.\n* Define \"created\" and \"updated\" columns in environment:list command.\n* Remove interactivity check from installer.\n* Fix: environments with an empty name (e.g. '0') not being recognized.\n* Fix documentation in user:list, user:add and user:update commands.\n* Various changes and new features in certificate:list command:\n - only show non-expired certificates by default\n - add --ignore-expiry option for the previous behavior\n - add --exclude-domain option to filter out certificates from the list\n - add --pipe-domains option to list domains covered by certificates\n - add \"certs\" alias\n - define \"domains\" column (for the --columns option)", - "3.39.0": "New features:\n\n* Add tunnel:single command (allow opening a tunnel without posix/pcntl).\n\nBug fixes:\n\n* Fix bug where login to a new account does not log out from the old account.\n* Account for differing app roots in Drush aliases - allows generated Drush\n aliases to work with Platform.sh Enterprise environments.\n* Fix Drush 9 alias compatibility (and remove Drush 9-beta compatibility).", - "3.40.0": "New features:\n\n* Support multiple schemas per database relationship:\n - Add --schema option to `db:dump` and `db:sql` commands.\n - Include the service name and schema in the `db:dump` default filename.\n - Present a choice between schemas if no schema is specified.\n - Make `db:size` schema agnostic but warn about inaccessible schemas.\n\nBug fixes:\n\n* Use a different Drush alias root for site-local versions 8 or 9. Drush 9\n fails to find a Drupal root via a relative path in some circumstances.\n* Fix behavior of choice questions with no default in non-interactive mode.\n* Fix checkout command when there is only one branch.\n* Fix Drush lock files not being used in local:build command.\n\nOther changes:\n\n* Add some 3-letter command aliases:\n - activity:list => activities => act\n - environment:list => environments => env\n - project:list => projects => pro\n - variable:list => variables => var", - "3.41.0": "New features:\n\n* Better worker support:\n - Improve worker selection in the environment:ssh (ssh) command.\n - Add the --worker option to all mount commands.\n - Add the --worker option to the environment:logs (log) command.\n - If --app or --worker is not provided, the above commands will now prompt you\n to choose between the app(s) or their worker(s).\n* Allow a nonexistent --target directory in the mount:download command (prompt\n to create it).\n\nBug fixes:\n\n* Add environment commit SHA to the deployment cache key\n This ensures old deployment information is not used when we know the environment\n has changed.\n* Ensure migrated projects on a new host do not use cached information from the\n old host.\n* Installer: the default config file should be .bash_profile on OS X.\n\nOther changes:\n\n* Update autocompletion library (stecman/symfony-console-completion)\n Highlights:\n - \"Adds support for quoted and escaped multi-word completions\"\n - \"Fixes options appearing before the command name (eg. `program -v cmdname`)\n breaking the detection of the command that should be completed for\"", - "3.42.0": "* Support older shells in autocompletion setup (like Bash 3.x, installed by default on OS X).\n* Accept wholly indented YAML files under the !include syntax.\n* Pass max_allowed_packet setting to mysqldump.\n* Require confirmation when adding a git source integration (GitHub, Bitbucket or GitLab).\n* Improve GIT_SSH argument quoting (may improve old Git version compatibility).\n* Fix ssh for environments with broken config.\n* Support installing versions with unstable suffixes in installer.\n* Warn in installer about missing pcntl and posix extensions.\n* Display nothing rather than a placeholder for sensitive values in variable:list tsv/csv format.\n* Add beta (hidden for now) source-operation:run command.", - "3.43.0": "* Allow some commands to read from local environment variables when available:\n * `route:list` (routes) and `route:get` can read from PLATFORM_ROUTES\n * `relationship:list` (relationships) can read from PLATFORM_RELATIONSHIPS\n * New `decode` command, to decode a base64- and JSON-encoded string such as\n PLATFORM_VARIABLES, PLATFORM_APPLICATION, etc.\n* Add --id and --primary options to route:get command, to select a route.\n* Add `url` property to all relationships in `relationships` command (e.g. to\n extract a DSN for a database).\n* Multiple changes to the `db:size` command (by @matthiaz):\n * Improve accuracy of size estimates by altering psql commands and querying\n for InnoDB-specific information.\n * Add --bytes (-B) option to display sizes in bytes.\n * Check if InnoDB tables can be optimized, and run optimizations\n automatically with the `--cleanup` option.", - "3.44.0": "New features:\n\n* Allow the `app:config` command to read from PLATFORM_APPLICATION in the\n local environment.\n* Improvements to the `environment:url` (`url`) command:\n * Read URLs from PLATFORM_ROUTES in the local environment.\n * Always prefer the primary route URL when sorting URLs.\n * Add a --primary option, to print or open only the primary route URL.\n * Just print URLs (without interaction) when there is no display/browser\n available.\n\nOther changes:\n\n* Prefer the primary route as the site URL (for Drush aliases, etc.).\n* Use embedded user account information if available in the access API (small\n performance improvement).\n* Fix \"Failed to decode\" in the `relationships` command when there are no\n relationships.\n* Adapt `welcome` command for offline-on-container uses. If you install the CLI\n into your own Platform.sh project, running `platform` on its own now gives\n you a list of useful commands that work \"offline\" without needing to\n configure an API token.\n* Do not require login for the `self:stats` command.\n* Improve app/worker choice message.\n* Clearer error for no config (`.platform.app.yaml`) file.\n* Match user email addresses case-insensitively. This fixes all user commands\n that need an email address (user:add, user:get, user:update and user:delete).", - "3.45.0": "New features:\n\n* Add --all (-a) option to mount:download command. This lets you download files\n from all of an application's mounts.\n* Enable gzip encoding in all API requests, if the zlib PHP extension is\n available. This should result in a performance improvement, particularly for\n slow connections or API responses containing a lot of data (e.g. the\n activity:list or environment:list commands).\n\nOther changes:\n\n* In mount:upload, convert OS X filename characters using rsync's `--iconv`.\n* Small bug fix for commit and repo commands: do not fetch an empty commit\n hash (after stripping commit parents notation).\n* Installer: fix check for git on Windows.", - "3.46.0": "New features:\n\n* Add --charset option to the db:dump command.\n Specifying the character set is recommended for any database dump.\n If your application database is in MySQL's utf8mb4, for example, use:\n platform db:dump --charset utf8mb4\n* Add --branch option to the 'push' command.\n This allows a new environment to be created as an active branch from a\n specified parent - thus inheriting settings from that parent - rather than\n setting the parent later.\n* Add bitbucket_server integration support.\n\nOther changes:\n\n* Installer:\n - Add --shell-type option to specify 'bash' or 'zsh' to the installer, for\n example:\n curl -sfS https://platform.sh/cli/installer | php -- --shell-type bash\n - Add --manifest option to use a test or development manifest file.\n - Require the curl, openssl and pcre PHP extensions (and stop requiring\n mbstring).\n - The --dev option will now allow any version string.\n* Rename \"Capacity\" to \"% Used\" in mount:size command.\n Also makes column machine names consistent with the db:size command.", - "3.47.0": "* Allow service commands to work 'locally' on the container\n \n It's now possible to use the following commands on the Platform.sh container\n without needing to have an API token or log in to the API:\n\n * db:dump\n * db:sql (sql)\n * service:redis-cli (redis)\n * service:mongo:dump (mongodump)\n * service:mongo:export (mongoexport)\n * service:mongo:restore (mongorestore)\n * service:mongo:shell (mongo)\n \n N.B.: When running locally the db:dump and db:sql commands have less schema\n information, so you may need to specify the --schema option.\n \n Commands that could already run on the container are:\n\n * help, list\n * app:config\n * decode\n * environment:url (url)\n * relationship:list (relationships)\n * route:get\n * route:list (routes)\n\n* Alias --primary to -1 in route:get and environment:url (url) commands.\n \n* Use primary route URL from deployments API in Drush aliases, when possible.\n \n* Allow multiple command arguments in drush and ssh commands.", - "3.48.0": "* Rename 'snapshot' commands to 'backup', to match the API and Management Console.\n* Implement streaming activity logs.\n - Add --timestamps (-t) option to the activity:log command.\n - Report the real start time in the activity progress bar.\n* Significantly reduce default cache lifetimes (due to API performance improvements).", - "3.49.0": "New features:\n\n* Allow the mount:list and mount:size commands to run locally on the container.\n\nOther changes:\n\n* Allow the SHA hash to wrap in the commit:list (commits) table.\n* Improve help for old, deprecated variable commands.\n* Refine description of --yes and --no options.\n* Deprecate 3 unused options in the commit:get command.\n* Fix --schema help in the db:sql command.\n* Make activity success/failure messages consistent.\n* Fix URL formatting in tunnel:single command output.\n* Fix property not found error in decode command with an empty object.\n* Display simple table (no messages) in integration:list if the --format is tsv or csv.\n* Changes to the environment:push (push) command:\n - Improve deprecation message for the --branch option.\n - Fix swapped question text for new/existing environments.\n - Wait for remaining activities.\n* Changes relating to waiting for multiple activities:\n - Fix activities being lost when reporting status after waiting.\n - Switch to the activity log stream for failed activities after waiting.\n - Increase timeout for initially fetching the log stream.", - "3.50.0": "New features:\n\n* Add an environment:scp (scp) command to transfer individual files.\n* Provide project choice in 'project:set-remote' command.\n* Add api:curl command to use the API directly.\n* Suggest to set the project as the remote, when 'get' command is used in an\n existing repository.\n* Add --set-remote and --no-set-remote options to project:create (create)\n command, which set the new project as the remote if called from an existing\n repository.\n* Provide interactive choice in variable:get (vget) command.\n* Add 'Enabled' (is_enabled) column in variable:list (var) command.\n* Add 'Machine name' (machine_name) column (not default) in environment:list\n (env) command.\n\nBug fixes:\n\n* Revert change that made Drush alias paths relative: they are now absolute\n again, as the relative paths did not work with the Drush sql-sync or rsync\n commands (#873).\n* Update API client to standardize project git URLs.\n* Do not require Git to find a git root.\n* Fix 'get' command detecting a project conflict in a separate directory.\n* Do not URL-encode the repository in Bitbucket API requests.\n* Add Docker setup for local development.\n* Refresh environments in the welcome command ('platform' on its own).", - "3.51.0": "New features:\n\n* Add PLATFORMSH_CLI_NO_INTERACTION environment variable, which can be used to\n disable interactivity everywhere (warning: important confirmation questions\n would be bypassed).\n* Add health.webhook integration type.\n\nOther changes:\n\n* Fix: avoid listing duplicate activities and avoid an infinite loop in\n activity:list (activities) command.\n* Remove handling of activity \"log\" property.\n* Fix init command example GitHub URL.\n* Update the project git URL when adding/deleting an integration.", - "3.52.0": "New features:\n\n* Add an `environment:xdebug` (`xdebug`) command, which opens an SSH tunnel to\n help debug PHP applications. The application must be configured with an\n xdebug trigger key (help is available by running the command).\n\nImprovements to Windows installation\n\n* Generate a `platform.bat` file during installation (the `self:install`\n command) so `platform` can be found in the Path.\n* Use \\ as the directory separator in user-facing installation paths.\n* Fix browser login: two bugs that prevented starting the local server, and\n preventing opening the URL in the browser.\n* Improve installation help messages for setting up the Path environment\n variable when using the Command Prompt.", - "3.53.0": "New features:\n\n * Use Docker credential helpers to store secrets.\n There are 3 Docker credential helpers available from:\n https://github.com/docker/docker-credential-helpers\n\n These allow storing secrets in a system appropriate way: Keychain on OS X,\n the libsecret keyring on Linux (the GNOME keyring), and the Credential\n Manager on Windows.\n\n This falls back to the previous file-based storage for systems incompatible with\n the credential helpers.\n\n * Add integration:activity commands:\n - integration:activity:get\n - integration:activity:list (i:act)\n - integration:activity:log\n\n * Add API token login command (auth:api-token-login).\n\n * Supply SSH options to mount:upload and mount:download commands.\n This allows specifying an SSH key with the --identity-file (-i) option to\n these commands.\n\nOther changes:\n\n * Remove password login help (the auth:password-login command is deprecated).\n * xdebug: change \"key\" to \"idekey\".\n * Prefer .zshrc rather than .zprofile for installation in ZSH.\n * Show the command output for failed processes to help debugging.", - "3.54.0": "New features:\n\n* Automatically prompt for login when token refresh fails.\n\nOther changes:\n\n* Fix deleting httpaccess settings via the environment:httpaccess command.\n* Remove the unused --environment option for integration:activity commands\n This also stops the commands unnecessarily prompting for an environment.\n* Tweak help for browser login.\n* Remove default From address for health.email notifications\n The API now provides a sensible default.", - "3.55.0": "New features:\n\n* Add activity scripts (`script`) integration type\n\nOther changes:\n\n* Suppress \"Link not found: #manage-access\" exception in backup:create command.\n* Clarify deprecation message for the --type option on the integration:update command.\n* Make integration:update command determine changes correctly.", - "3.56.0": "New features:\n\n* Support an option for building draft pull requests in the GitHub integration,\n and the equivalent (building WIP merge requests) for GitLab.\n* Add automatic SSH certificate support (disabled by default).\n\nOther changes:\n\n* Add `environment:deactivate` as an alias of the `environment:delete` command.\n* Fix error in command listing, when the .platform.app.yaml is not found", - "3.57.0": "New features:\n\n* Improve SSH key commands\n - Provide a choice and a more meaningful key name when generating a key in the\n `ssh-key:add` command.\n - Clarify the account that an SSH key is going to be added to.\n - Append the `.pub` extension automatically.\n - Show the local path (if present) in the `ssh-key:list` (`ssh-keys`) command.\n - Ensure session-based SSH configuration is up to date, when running any SSH command.\n* Automatically select an SSH key from `~/.ssh` corresponding to the user\n account when there are multiple identities available, and specify it in SSH\n commands.\n* Provide an interactive choice in the `integration:update` command when no\n integration ID is specified.\n* Add a `session:switch` command (beta), which helps change the session when\n managing multiple accounts. When there is more than one session present,\n information (session ID and logged-in user email) is displayed in various\n relevant commands.\n\nOther changes:\n\n* Fix irrelevant 403 errors when the user does not have access to the current\n project, but they specified another project in the command line anyway.\n* Include only one session's SSH certificate configuration.\n* Fix use of certifier URL configuration.\n* Remove the command `auth:password-login`, which no longer works.", - "3.58.0": "* Support prompting for re-authentication in the login command.\n The command `platform login --force` (or `-f`) will now ask the\n authentication server to prompt the user to log in again, even if they\n already had a session in that browser. This is helpful for refreshing\n authentication status (e.g. to re-verify 2FA), or for logging in as another\n account.\n* The region list in the `platform create` command is now sorted\n alphabetically (with the subdomain and suffix sorted separately).\n* Add `--count` (`-c`) option to the `self:stats` command to modify the results per page.\n* Move the SSH hostname to the session-specific SSH configuration. This means\n less rewriting needed for the global `~/.ssh/config` file when changing CLI\n configuration.", - "3.59.0": "* Support new user invitations\n* Installer improvements:\n - Create a .zshrc file if it doesn't already exist, on ZSH.\n - Require the mbstring extension.", - "3.60.0": "New features:\n\n* Autoload SSH certificates on login or SSH, by default.\n When supported by all relevant servers (soon), SSH certificates will:\n - Enable support for multi-factor authentication on projects that require it for SSH access.\n - Make it no longer necessary to add or administer SSH keys manually.\n\n Note: SSH certificates can be checked or reloaded using the `ssh-cert:load` command.\n\nOther changes:\n\n* SSH configuration improvements:\n - Denote top-level SSH configuration with a `Host` block, to avoid issues where a previous `Host` or `Match` configuration had not been closed.\n - End the Host blocks explicitly with `Host *`\n* Fix detection of third-party Git domains for `project:get` (`get`) command errors.", - "3.61.0": "New features:\n\n* Project choice will now be via an autocomplete, rather than a list, if there\n are too many to list (more than 25, or more than the terminal height). Start\n typing a project ID, or use the up and down arrows to scroll through the\n autocomplete suggestions.\n* Similarly, the environment choice can now be via a list, if there are few\n enough to display like that, otherwise the older autocomplete will show.\n* The installer now supports a proxy defined with one of the `https_proxy` or\n `http_proxy` environment variables.\n\nOther changes:\n\n* Use --globoff by default in curl commands\n* Disable use of the SSH agent unless explicitly enabled in the user config file.\n Add this to the user config file to automatically add keys/certificates to the SSH agent:\n ```yaml\n # ~/.platformsh/config.yaml\n api:\n add_to_ssh_agent: true\n ```\n* Explicitly request the `offline_access` scope when logging in.\n* Add a comment to auto-generated temporary SSH keys.", - "3.62.0": "* Support force-sending an invitation in the `user:add` command (via a prompt,\n or the `--force-invite` option).\n* Retry fetching the activity log stream to avoid errors when an activity is starting.\n* Show the stderr of every shell command, if in debug mode (`-vvv`).\n* Handle multi-host mongodb URIs in relationships.", - "3.63.0": "New features:\n\n* Add `--gateway-ports` (`-g`) option to tunnel commands. This allows tunnels\n to be accessed from Docker containers for local development.\n* Installer: auto-detect existing but disabled extensions.\n\nOther changes:\n\n* Stop using SSH home directory substitution. This fixes an SSH issue in\n GitHub Actions.\n* Fix missing Git detection on Windows. This improves the `get` command error\n message when Git is not installed.\n* Fix `db:size` command on 32-bit platforms (handling sizes greater than 2GB).", - "3.64.0": "New features:\n\n* Add MongoDB support to the `db:size` command.\n\n* Add `--unsafe` option to the `backup:create` (`backup`) command.\n If set, this leaves the environment running and open to connections during\n the backup. This reduces downtime, at the risk of backing up data in an\n inconsistent state.\n N.B. This will only work for more recent versions of our orchestration\n software. Upgrades are mostly taking place this week.\n\n* Add `--state` and `--result` filters to activity commands.\n Possible states: `in_progress`, `pending`, `complete`, or `cancelled`.\n Possible results: `success` or `failure`.\n N.B. The `--result` filter cannot be combined with others at this time.\n\n* Add an `activity:cancel` command, which will allow cancelling pending\n activities, and stopping crons that are in progress.\n\n* SSH error handling improvements:\n - Recommend an SSH certificate if not present after an authentication error.\n - Avoid making a test connection where not necessary.\n - Check for host key authentication errors, and suggest remedies for CI scripts.\n\nOther changes:\n\n* Remove hardcoded instances of \"master\" as the default branch name.\n* Show a loading message on environments and project lists.", - "3.65.0": "New features:\n\n* Add --format=plain option for table commands. This produces TSV-like output, \n but with no quoting or escaping, to make most output easier to process with \n basic command-line tools such as cut and grep. Literal tabs and newlines are \n replaced by a space.\n\nOther changes:\n\n* Rename the backup --unsafe option to --live\n (This tones down the warning level on making such a backup)\n* Remove the restriction on deleting the main environment\n* Remove us-2 from available region examples", - "3.66.0": "* Use the 'http://' scheme instead of 'solr://' for Solr service URLs.\n* Add a --default-branch option to the project:create command.\n* Make the default API request timeout configurable through an env var.\n PLATFORMSH_CLI_DEFAULT_TIMEOUT will override the default of 30 (seconds).", - "3.67.0": "New features (which depend on the upcoming API version 12):\n\n* Support build-time environment-level variables.\n* Some support for environment types (see note below):\n - Add the type in the `environment:list` (`env`) command.\n - Mention the type in the environment label.\n - Add a `--type` option to the `push` command.\n - Add a `--type` option to the `branch` command.\n - Allow the type to be modified in the `env:info` command.\n\n> Note: user commands (such as `user:add` and `user:update`) do not yet support\nenvironment types. They will in a future version. The API is backwards\ncompatible: if your project supports environment types, and you change a user's\nrole on an environment, it will change their role on all environments of that\ntype.\n\nOther changes:\n\n* Support new `/users/me` API in the `auth:info` command.\n* Fix `user:update` to avoid recalculating environment access changes while they are made.", - "3.68.0": "New features:\n\n* Add environment types support to the user commands.\n\nOther changes:\n\n* Rework `environment:delete --merged` to not require a local Git repository.\n* Refresh the cache if a specified environment is not found in the `environment:delete` command.\n* Fix duplicates when listing multiple activity statuses.", - "3.69.0": "New features:\n\n* Add support for Organizations.\n This is disabled by default, unless enabled in `config.yaml` (`api.organizations`)\n or in an environment variable (`PLATFORMSH_CLI_API_ORGANIZATIONS`).\n\nOther changes:\n\n* Fix inviting a user to a project with an environment type permission.\n* When cloning an empty project via the `project:get` (`get`) command,\n initialize with the correct default branch name, rather than `master`.\n* Remove Commands section in README.\n This was already too long, and Organizations support adds many extra commands.", - "3.69.2": "* Enable use of the organizations API by default", - "3.70.0": "* Add org:curl hidden utility command\n* Improve labelling for org:user:get", - "3.71.0": "New features:\n* Allow updates to multiple properties on organization billing addresses\n\nOther changes:\n* Add detailed help to the ssh-cert:load command\n* Update client library to fix handling of project invitation conflicts\n* Remove references to Hipchat\n* Ignore 400 errors from getCurrentDeployment() in drush-aliases command", - "3.72.1": "* Display the organization in the projects list.", - "3.72.2": "* Don't generate an alias for an inactive DG2 target (#1093)", - "3.73.0": "New features:\n\n* Add a hidden `env:curl` command to make raw requests to the current environment's API.\n* Add pagination to the `projects` (`project:list`) command.\n Also add a new optional `created_at` column.\n* Allow unsetting the remote project in the `project:set-remote` command (`project:set-remote -`).\n\nOther changes:\n\n* Warn when an API token is set on the container.\n* Remove workaround for an older PHP pcre.jit bug (fixed since PHP 7.3.11).\n* Add delimiter to read environment variables over SSH without pollution from other messages.\n* Fix TypeError in domain:add command error handling in PHP 8+ (#1097).\n* Search the docs site directly for the `docs` command.", - "3.73.1": "* Show release notes for each version on update", - "3.73.2": "* Support \"require\" and \"repositories\" keys for PHP dependencies", - "3.74.0": "* Add `org:create` and `org:delete` commands (marked as beta/alpha for now).\n* Add `auth:info id --no-auto-login` as a convenience for scripts to check if already logged in.", - "3.75.0": "New features:\n* Add `repo:read` (`read`) command to read a file or directory in the project's\n hosted Git repository (combining `repo:ls` and `repo:cat`).\n\nOther changes:\n* Use `composer/ca-bundle` to find a CA bundle automatically (for TLS\n certificate verification on systems without a CA bundle configured).\n* Avoid prompting to create an SSH config file, if the file is not writable.\n* Avoid printing all release notes when upgrading from a dev version\n* Allow using the cache for finding an environment by machine name.\n* Print default environment/organization selection to STDERR.", - "3.75.1": "* Add the bundled cacert.pem to the phar build", - "3.75.2": "* Use Composer's CA bundle detection in additional places:\n - the credential helper download\n - the self-updater (`up` command)\n - the installer script (detection only, without the fallback bundle)", - "3.75.3": "* Fix ConsoleLogger TypeError in Api class due to null stdErr property", - "3.76.0": "* Add country support to org:create and org:info commands.\n* Make the org:create and org:delete commands visible in the list.", - "3.76.1": "* Fetch organization-specific plans and regions lists when creating a project.\n This fixes a \"Multiple organizations found\" error when the user owns more\n than one organization.\n* Suppress browser stderr messages unless in `-vv` or debug mode.", - "3.76.2": "* Fix auto login in interactive mode \n Auto login was unintentionally disabled in v3.76.1 via a bug in the regions API changes.", - "3.77.0": "### `backup:list` (`backups`) command changes\n\nThis command now uses the backups API for listing backups, rather than the\nactivities API. The result is that deleted backups will no longer be listed\n(avoiding confusion), and the output changes slightly.\n\nOld version example:\n\n +---------------------------+----------------+----------+----------+---------+\n | Created | Backup name | Progress | State | Result |\n +---------------------------+----------------+----------+----------+---------+\n | 2022-02-01T00:00:00+00:00 | abcdefgh123456 | 100% | complete | success |\n +---------------------------+----------------+----------+----------+---------+\n\nNew version example:\n\n +---------------------------+----------------+-------------+\n | Created | Backup ID | Restorable |\n +---------------------------+----------------+-------------+\n | 2022-02-01T00:00:00+00:00 | abcdefgh123456 | true |\n +---------------------------+----------------+-------------+\n\nNew available `--columns` names: `created_at`, `id`, `restorable`, `expires_at`, `commit_id`, `index`, `safe`\n\nDeprecated options (which no longer have an effect): `--limit`, `--start`\nDeprecated column names (which no longer show data): `progress`, `state`, `result`", - "3.78.0": "New features:\n\n* Display some more information about regions in the project:create command, \n including the datacenter's location, provider and carbon intensity.\n* Improve environment:delete command:\n - Add --type and --exclude-type options\n - Include environment(s) when --inactive or --merged are specified\n - Split the environment value by commas, in the 'environment' argument\n - Make the reason for environments being found clearer\n - Handle all environment statuses\n\nOther changes:\n\n* Improve error for empty environments in repo: commands.\n* Use the organizations API more consistently in the subscription:info command.\n* Sort the result in the backup:list (backups) command.", - "3.79.0": "New features:\n\n* Add an `--exclude-type` option when listing activities, and support multiple\n types in `--type`.\n\nOther changes:\n\n* Fix too many API calls in `backup:restore` command when finding a backup by name\n* Fix activity pagination skipping items created at the same second (add microseconds support).", - "3.79.1": "* Fix error in activity commands: \"The value of option --type is not an array\"", - "3.79.2": "* Fix 'The \"incomplete\" option does not exist' in the integration:activities command", - "3.79.3": "* Fix organization support in the `project:delete` command.\n Removes the now inaccurate advice, \"Only the project's owner can delete it\".\n* Fix wildcard matching for environment IDs that contain a slash (`/`).\n Affected commands: `environment:delete` and `user:add`.\n* Print the current environment in the `environment:delete` command.", - "3.79.4": "* Handle the \"cancelled\" state in the `activity:log` command.\n* Allow manual override of the region ID in the `project:create` command.\n* Always use the API gateway URL (\"external API proxy\") when configured.\n* Error when a conditional field is used in the wrong context.\n For example, when the `--fetch-branches` option is used with the `webhook`\n integration type, previously it would have been silently ignored, now it\n will fail with an error message.\n Affected commands:\n `integration:add`, `integration:update`, `variable:create`, `variable:update`\n* Allow pagination to be disabled in the `project:list` (`projects`) command.\n Pagination for this command can be disabled using an environment variable:\n\n export PLATFORMSH_CLI_PAGINATION_ENABLED=0\n\n or by adding the following to `$HOME/.platformsh/config.yaml`:\n\n pagination:\n enabled: false\n* Return a 0 exit code from `environment:delete` if no error occurred.", - "3.79.5": "* Fix: \"pagination\" section in config.yaml could not override the default\n* Fix potentially not using the cache when a project hostname is known\n* Fix \"Call to a member function setApiUrl() on bool\"", - "3.79.6": "* Update client to fix SSH URL problem introduced in v3.79.4.", - "3.79.7": "* Fix an additional bug introduced in v3.79.4: the activity log ending abruptly\n without refreshing the activity state.\n* Avoid errors caused by invalid config when processing environment variables.", - "3.79.8": "* Add --if-exists to pg_dump, in the db:dump command, so that PostgreSQL dump\n files can be restored without errors on an empty database.\n* Avoid matching \"-\" as an environment ID when parsing a Console URL.\n* Fix legacy region detection in the backup:restore command, preventing\n accidental restore to the wrong environment on such regions.", - "3.80.0": "* Support the `--shared-key` option for `webhook` as well as `health.webhook`\n integrations.\n* Fix SSL chain validation for files containing multiple certificates.\n* Improvements to the `environment:delete` command:\n - Only select the current environment if nothing else is specified.\n This may change behavior for existing scripts if they expect the\n currently checked-out environment to be always selected for deletion.\n - Avoid asking twice about deleting the same environment.\n - Add an `--only-type` option, to enforce only deleting environments of\n one or more types, e.g.\n\n platform env:delete --merged --only-type development", - "3.80.1": "* Fix inverted --exclude in the environment:delete command\n (a bug introduced in v3.80.0)", - "3.80.2": "* Filter by organization (`--org`) without needing direct access, in the\n `project:list` (`projects`) command.\n* Display the simpler version of the project directory in the `project:get`\n (`get`) command.", - "3.80.3": "* Translate environment ID to type when adding or changing a user. \n Since around 1 year ago with the introduction of environment types, users\n should be given roles on the environment type, not on a specific environment\n ID. For those still using environment IDs, the CLI now translates these to\n types on the client side (before the API call), for more clarity in the\n output and when later listing project invitations.\n* Remove support for projects which do not have environment types (such\n projects no longer exist).\n\nMore information on environment types: https://docs.platform.sh/administration/users.html#environment-types", - "3.81.0": "New features:\n\n* Add `--no-warn` (`-W`) and `--header` (`-H`) options to the `auth:token` command.\n* Add `--json` option to curl commands.\n* Return a success (zero) exit code from `self:update` if there is no update.\n\nOther changes:\n\n* Fix \"Property defined twice\" error in the `org:billing:address` command.\n* Clarify no organization message in `project:create` (`create`) command.\n* Remove more now unnecessary checks for environment types.\n Improves performance in `environment:list` (`env`) command.\n* Fix example for `user:get --level environment`.", - "3.81.1": "* Fix: automatic update prompt not saving last check time if there is no update\n* Remove mbstring requirement from README and installer\n* Add environment.domain activities to the type filter\n* Allow use of --json option with lower curl versions", - "3.82.0": "* Support pagination in the `organization:subscriptions` command.\n Add `--page` and `--count` (`-c`) options. A `0` count disables pagination.\n* Adapt the `project:list` (`projects`) command for consistency:\n - Specifying a `--page` will work even if pagination had been otherwise disabled.\n - Remove terminal height detection, instead making the \"items per page\" count\n a configuration parameter defaulting to 20 items, overridden by `--count`.\n* Clarify environment variables when the home directory cannot be detected.\n* Remove now unnecessary webhook code in `integration` commands, fixing a bug\n where source code integration webhooks were added with the incorrect URL.", - "3.82.1": "* Improve pagination description in the `org:sub` command.\n* Clarify `--my` option in `project:list` (`projects`) for organizations.\n* Add `integration.health.webhook` activity type.\n* Fix `commit:list` (`commits`) to respect `--limit` for commits with\n multiple parents.\n* Replace templated values when installing shell config files. This only\n affects alternative configurations, e.g. dev builds.", - "3.82.2": "* Display the error from Xcode in the project:get (get) command\n* Clarify \"switch remote\" vs \"set remote\" in the project:create (create) command\n* Fix env:delete command to respect --no-delete-branch in interactive mode", - "3.83.0": "New features:\n\n* Add --instance (-I) option to SSH commands.\n\nOther changes:\n\n* Disable the credential helper when running in a snap (e.g. an IDE on Ubuntu),\n as it appears to work inconsistently (reading yet not writing).\n* Allow interactive choice of workers for SSH to Dedicated environments.\n* Rework fetching projects\n - Separate project \"stub\" from full project representations to avoid using the\n stub information accidentally. Fixes inconsistent marking a user as the\n project \"owner\" in the users:list (users) command.\n - Fixes the validation error when asked for a project ID interactively.", - "3.83.1": "* Quick fix for: 'The \"instance\" option does not exist' in tunnel commands", - "3.84.0": "New features:\n\n* Add support for third-party log forwarding integrations (API availability\n depends on the project): newrelic, sumologic, splunk, and syslog.\n* Add metrics:disk-usage (disk) command.\n\nOther changes:\n\n* Use the API URL consistently to fetch a project even if a direct hostname is\n supplied. This deprecates the `--host` option (the option is still there but\n will be ignored).\n* Bump composer/ca-bundle from 1.3.3 to 1.3.4 (#1170).\n* Disable docker credential helper inside a container (where it does not work).\n* Ensure Drush alias names only contain allowed characters. This fixes aliases\n for environment names containing a slash.\n* Find the correct app's SSH URL when no instance or default is given.", - "3.84.1": "* Fix integration:create command: auth_token and auth_mode are only for syslog integrations", - "3.84.2": "* Hide unavailable integrations (per project) in the integration:create command\n* Simplify prompts for the syslog and other log integrations\n* Add clarity to the --yes flag in help", - "3.85.0": "* Accept multiple arguments in the \"multi\" command. This means that the command\n to run multiple times can be specified after -- without needing to use\n quotes.\n* Fix listing custom schema (database) names for postgresql in the db:dump command.\n* Fix PHP 5.6 incompatibility in the db:size command.\n* Fix Dedicated environment detection in the logs command.\n* Fix inconsistency between -e and --environment in the variable:create command.", - "3.86.0": "* Improve default options and their documentation:\n - Add a `--no-interaction` option (to match Symfony Console defaults a little\n more closely), with the same meaning as enabling the `PLATFORMSH_CLI_NO_INTERACTION`\n environment variable: disable interaction and pick the default answer to\n questions. The existing `--yes` (`-y`) option is similar in meaning and now\n the same in effect; the answer \"yes\" has been made the default for all\n confirmation questions.\n - Hide deprecated options from command help (still with no error if they are\n used). This includes `--no` (`-n`), which can be confused with `--no-interaction`\n though it is subtly different: it answers \"no\" to all confirmation questions,\n and picks the default answer for other questions. This also restores `--ansi`\n and `--no-ansi` as hidden options; they are present by default in Symfony\n Console but were previously omitted from this CLI for simplicity.\n - See `platform help [cmd]` for more information.\n* Improve the git tree cache for commands such as `read`.\n* Show the `disk` command in the list.\n* Two fixes to the `multi` command:\n - Fix dialog-based project selection (previously resulting in 0 projects)\n - Fix non-interactive use: e.g. the `-y` flag now works for the parent and\n child commands independently.", - "3.86.1": "* Avoid a TypeError when reading invalid applications.yaml configuration\n* Clarify activity cannot be cancelled error\n* Fix \"multi\" command exiting after the first project", - "3.87.0": "* Integrate with the phone number verification API:\n - Check if phone number verification is required in the\n `project:create` (`create`) command.\n - Add an `auth:verify-phone-number` command to verify the number\n interactively.", - "3.87.1": "* Refresh phone verification status after verifying", - "3.88.0": "* Add support for automatic \"self-installation\" of shell configuration files.\n* Fix auto-login (broken in v3.87.0).", - "3.88.1": "* Avoid the self-install prompt in more situations:\n - In all cases where the self:install command previously exited successfully.\n - If environment variables are set:\n `PLATFORMSH_CLI_APPLICATION_PROMPT_SELF_INSTALL=0` or `PLATFORMSH_CLI_WRAPPED=1`" - } - } -] \ No newline at end of file diff --git a/legacy/phpstan-baseline.neon b/legacy/phpstan-baseline.neon index 128f1d365..2c73339d9 100644 --- a/legacy/phpstan-baseline.neon +++ b/legacy/phpstan-baseline.neon @@ -1,11 +1,5 @@ parameters: ignoreErrors: - - - message: '#^Binary operation "\." between ''https\://'' and mixed results in an error\.$#' - identifier: binaryOp.invalid - count: 1 - path: dist/dev-build-index.php - - message: '#^Binary operation "\." between ''http\://127\.0\.0\.1\:'' and mixed results in an error\.$#' identifier: binaryOp.invalid @@ -3456,174 +3450,6 @@ parameters: count: 1 path: src/Command/Self/SelfInstallCommand.php - - - message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#' - identifier: foreach.nonIterable - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Binary operation "\." between ''Release v'' and mixed results in an error\.$#' - identifier: binaryOp.invalid - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Binary operation "\." between non\-falsy\-string and mixed results in an error\.$#' - identifier: binaryOp.invalid - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Cannot access offset ''min'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Cannot access offset ''name'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Cannot access offset ''notes'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Cannot access offset ''php'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Cannot access offset ''sha1'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Cannot access offset ''sha256'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Cannot access offset ''url'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Cannot access offset ''version'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 6 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Cannot access offset 0 on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Cannot access offset mixed on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 3 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Cannot cast mixed to string\.$#' - identifier: cast.string - count: 3 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Parameter \#1 \$array of function array_unshift expects array, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Parameter \#1 \$messages of method Symfony\\Component\\Console\\Output\\OutputInterface\:\:writeln\(\) expects iterable\|string, string\|null given\.$#' - identifier: argument.type - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Parameter \#1 \$version of method Platformsh\\Cli\\Util\\VersionUtil\:\:isPreRelease\(\) expects string, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Parameter \#1 \$version of method Platformsh\\Cli\\Util\\VersionUtil\:\:majorVersion\(\) expects string, mixed given\.$#' - identifier: argument.type - count: 2 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Parameter \#1 \$version1 of function version_compare expects string, mixed given\.$#' - identifier: argument.type - count: 3 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Parameter \#2 \$version2 of function version_compare expects string, mixed given\.$#' - identifier: argument.type - count: 2 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Parameter \#3 \$subject of function str_replace expects array\\|string, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/Command/Self/SelfReleaseCommand.php - - - - message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#' - identifier: foreach.nonIterable - count: 1 - path: src/Command/Self/SelfStatsCommand.php - - - - message: '#^Cannot access offset ''assets'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Command/Self/SelfStatsCommand.php - - - - message: '#^Cannot access offset ''created_at'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Command/Self/SelfStatsCommand.php - - - - message: '#^Cannot access offset ''download_count'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Command/Self/SelfStatsCommand.php - - - - message: '#^Cannot access offset ''name'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 2 - path: src/Command/Self/SelfStatsCommand.php - - - - message: '#^Cannot access offset ''published_at'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Command/Self/SelfStatsCommand.php - - - - message: '#^Parameter \#1 \$rows of method Platformsh\\Cli\\Service\\Table\:\:render\(\) expects array\\|Symfony\\Component\\Console\\Helper\\TableSeparator\>, list\ given\.$#' - identifier: argument.type - count: 1 - path: src/Command/Self/SelfStatsCommand.php - - message: '#^Binary operation "\+" between mixed and array\{original_url\: string\} results in an error\.$#' identifier: binaryOp.invalid @@ -5694,66 +5520,6 @@ parameters: count: 4 path: src/Selector/Selector.php - - - message: '#^Cannot access offset ''timeout'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 2 - path: src/SelfUpdate/ManifestStrategy.php - - - - message: '#^Cannot access offset ''version'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/SelfUpdate/ManifestStrategy.php - - - - message: '#^Cannot cast mixed to string\.$#' - identifier: cast.string - count: 2 - path: src/SelfUpdate/ManifestStrategy.php - - - - message: '#^Method Platformsh\\Cli\\SelfUpdate\\ManifestStrategy\:\:getAvailableVersions\(\) should return array\\|string\}\> but returns array\\.$#' - identifier: return.type - count: 1 - path: src/SelfUpdate/ManifestStrategy.php - - - - message: '#^Parameter \#1 \$array of function array_keys expects array, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/SelfUpdate/ManifestStrategy.php - - - - message: '#^Parameter \#1 \$filename of function file_get_contents expects string, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/SelfUpdate/ManifestStrategy.php - - - - message: '#^Parameter \#1 \$filename of function file_put_contents expects string, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/SelfUpdate/ManifestStrategy.php - - - - message: '#^Parameter \#2 \.\.\.\$values of function sprintf expects bool\|float\|int\|string\|null, mixed given\.$#' - identifier: argument.type - count: 3 - path: src/SelfUpdate/ManifestStrategy.php - - - - message: '#^Possibly invalid array key type mixed\.$#' - identifier: offsetAccess.invalidOffset - count: 1 - path: src/SelfUpdate/ManifestStrategy.php - - - - message: '#^Property Platformsh\\Cli\\SelfUpdate\\ManifestStrategy\:\:\$availableVersions \(array\\>\|null\) does not accept array\\.$#' - identifier: assign.propertyType - count: 1 - path: src/SelfUpdate/ManifestStrategy.php - - message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#' identifier: foreach.nonIterable @@ -7044,36 +6810,6 @@ parameters: count: 1 path: src/Service/Rsync.php - - - message: '#^Method Platformsh\\Cli\\Service\\SelfUpdater\:\:update\(\) should return string\|false but returns mixed\.$#' - identifier: return.type - count: 1 - path: src/Service/SelfUpdater.php - - - - message: '#^Parameter \#1 \$messages of method Symfony\\Component\\Console\\Output\\OutputInterface\:\:writeln\(\) expects iterable\|string, string\|null given\.$#' - identifier: argument.type - count: 1 - path: src/Service/SelfUpdater.php - - - - message: '#^Parameter \#2 \$targetVersion of method Platformsh\\Cli\\SelfUpdate\\ManifestStrategy\:\:getUpdateNotesByVersion\(\) expects string, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/Service/SelfUpdater.php - - - - message: '#^Parameter \#2 \.\.\.\$values of function sprintf expects bool\|float\|int\|string\|null, mixed given\.$#' - identifier: argument.type - count: 3 - path: src/Service/SelfUpdater.php - - - - message: '#^Parameter \#3 \.\.\.\$values of function sprintf expects bool\|float\|int\|string\|null, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/Service/SelfUpdater.php - - message: '#^Cannot access offset non\-empty\-string on mixed\.$#' identifier: offsetAccess.nonOffsetAccessible diff --git a/legacy/phpstan.neon b/legacy/phpstan.neon index b037373fe..34f896b38 100644 --- a/legacy/phpstan.neon +++ b/legacy/phpstan.neon @@ -1,15 +1,11 @@ parameters: level: 10 paths: - - dist - resources - src - tests scanFiles: - config/cache/container.php - - dist/installer.php - excludePaths: - - dist/installer.php stubFiles: - phpstan-stubs/Activity.stub - phpstan-stubs/Command.stub diff --git a/legacy/scripts/test/build-and-install.sh b/legacy/scripts/test/build-and-install.sh deleted file mode 100755 index 21d47b077..000000000 --- a/legacy/scripts/test/build-and-install.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -# Tests building and installing a test version of the CLI. -# This must be run from the repository root. - -export version=test - -# Ensure Composer dependencies including Box. -if [ ! -d vendor ]; then - composer install -fi -if [ ! -d vendor-bin/box/vendor ]; then - composer -d vendor-bin/box install -fi - -function cleanup { - rm tmp-platform.phar - rm tmp-manifest.json -} -trap cleanup EXIT - -# Build the CLI. -./bin/platform self:build --no-composer-rebuild --yes --replace-version "$version" --output tmp-platform.phar - -# Create a manifest file. -export sha256="$(shasum -a 256 tmp-platform.phar | cut -f1 -d' ')" -cat < tmp-manifest.json -[ - { - "version": "$version", - "sha256": "$sha256", - "name": "platform.phar", - "url": "tmp-platform.phar" - } -] -EOF - -# Run the installer. -cat ./dist/installer.php | php -- --manifest ./tmp-manifest.json --dev diff --git a/legacy/scripts/test/installer.sh b/legacy/scripts/test/installer.sh deleted file mode 100755 index 4f34c24e0..000000000 --- a/legacy/scripts/test/installer.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -# Tests the CLI installer. -# This must be run from the repository root. - -cat ./dist/installer.php | php -- --manifest ./dist/manifest.json diff --git a/legacy/src/Application.php b/legacy/src/Application.php index 382eeabff..99f26d94f 100644 --- a/legacy/src/Application.php +++ b/legacy/src/Application.php @@ -17,7 +17,6 @@ use Platformsh\Cli\Service\Config; use Platformsh\Cli\Service\LegacyMigration; use Platformsh\Cli\Service\SelfInstallChecker; -use Platformsh\Cli\Service\SelfUpdateChecker; use Platformsh\Cli\Util\TimezoneUtil; use Symfony\Component\Config\FileLocator; use Symfony\Component\Console\Application as ParentApplication; @@ -549,14 +548,8 @@ protected function doRunCommand(ConsoleCommand $command, InputInterface $input, $input->setInteractive(false); } - // Check for automatic updates. $noChecks = $command->getName() == '_completion'; $container = $this->container(); - if ($input->isInteractive() && !$noChecks) { - /** @var SelfUpdateChecker $checker */ - $checker = $container->get(SelfUpdateChecker::class); - $checker->checkUpdates(); - } if (!$noChecks && $command->getName() !== 'legacy-migrate') { /** @var LegacyMigration $legacyMigration */ diff --git a/legacy/src/Command/Self/SelfBuildCommand.php b/legacy/src/Command/Self/SelfBuildCommand.php index 2141c7a5f..5dd777dc0 100644 --- a/legacy/src/Command/Self/SelfBuildCommand.php +++ b/legacy/src/Command/Self/SelfBuildCommand.php @@ -75,10 +75,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $boxConfig['replacements']['version-placeholder'] = $version; - if (!$this->checkInstallerFile()) { - return 1; - } - if ($outputFilename) { $boxConfig['output'] = $this->filesystem->makePathAbsolute($outputFilename); } else { @@ -169,50 +165,4 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } - - /** - * Ensure the installer.php file has config that matches config.yaml. - * - * @return bool - */ - private function checkInstallerFile(): bool - { - $installerFile = CLI_ROOT . '/dist/installer.php'; - $installerContents = \file_get_contents($installerFile); - if ($installerContents === false) { - $this->stdErr->writeln('Failed to read installer file: ' . $installerFile . ''); - return false; - } - $start = "/* START_CONFIG */"; - $end = "/* END_CONFIG */"; - $commentStart = \strpos($installerContents, $start); - $startPos = $commentStart ? $commentStart + \strlen($start) : false; - $endPos = \strpos($installerContents, $end); - if ($startPos === false || $endPos === false || $endPos < $startPos) { - $this->stdErr->writeln('Failed to locate config in installer file: ' . $installerFile . ''); - return false; - } - $newConfig = \var_export([ - 'envPrefix' => $this->config->getStr('application.env_prefix'), - 'manifestUrl' => $this->config->getStr('application.manifest_url'), - 'configDir' => $this->config->getStr('application.user_config_dir'), - 'executable' => $this->config->getStr('application.executable'), - 'cliName' => $this->config->getStr('application.name'), - 'userAgent' => $this->config->getStr('application.slug'), - 'serviceEnvPrefix' => $this->config->getStr('service.env_prefix'), - 'migratePrompt' => $this->config->getBool('migrate.prompt'), - 'migrateDocsUrl' => $this->config->getStr('migrate.docs_url'), - ], true); - $newContents = \substr($installerContents, 0, $startPos) . $newConfig . \substr($installerContents, $endPos); - if ($newContents !== $installerContents) { - $this->stdErr->writeln('Modifying installer file to match config'); - if (!\file_put_contents($installerFile, $newContents)) { - $this->stdErr->writeln('Failed to write to installer file: ' . $installerFile . ''); - return false; - } - } else { - $this->stdErr->writeln('Verified installer file'); - } - return true; - } } diff --git a/legacy/src/Command/Self/SelfReleaseCommand.php b/legacy/src/Command/Self/SelfReleaseCommand.php deleted file mode 100644 index 8a84cfab1..000000000 --- a/legacy/src/Command/Self/SelfReleaseCommand.php +++ /dev/null @@ -1,490 +0,0 @@ -config->getStr('application.github_repo'); - $defaultReleaseBranch = $this->config->getStr('application.release_branch'); - - $this - ->addArgument('version', InputArgument::OPTIONAL, 'The new version number') - ->addOption('phar', null, InputOption::VALUE_REQUIRED, 'The path to a newly built Phar file') - ->addOption('repo', null, InputOption::VALUE_REQUIRED, 'The GitHub repository', $defaultRepo) - ->addOption('manifest', null, InputOption::VALUE_REQUIRED, 'The manifest file to update') - ->addOption('manifest-mode', null, InputOption::VALUE_REQUIRED, 'How to update the manifest file', 'update-latest-matching') - ->addOption('release-branch', null, InputOption::VALUE_REQUIRED, 'Override the release branch', $defaultReleaseBranch) - ->addOption('last-version', null, InputOption::VALUE_REQUIRED, 'The last version number') - ->addOption('no-check-changes', null, InputOption::VALUE_NONE, 'Skip check for uncommitted changes, or no change since the last version') - ->addOption('allow-lower', null, InputOption::VALUE_NONE, 'Allow releasing with a lower version number than the last'); - } - - public function isEnabled(): bool - { - return $this->config->has('application.github_repo') - && (!extension_loaded('Phar') || !\Phar::running(false)); - } - - /** - * @throws \Exception - */ - protected function execute(InputInterface $input, OutputInterface $output): int - { - $this->git->setDefaultRepositoryDir(CLI_ROOT); - - $releaseBranch = Option::string($input, 'release-branch'); - if ($this->git->getCurrentBranch(CLI_ROOT, true) !== $releaseBranch) { - $this->stdErr->writeln('You must be on the ' . $releaseBranch . ' branch to make a release.'); - - return 1; - } - - if (!Option::bool($input, 'no-check-changes')) { - $gitStatus = $this->git->execute(['status', '--porcelain'], CLI_ROOT, true); - if (is_string($gitStatus) && !empty($gitStatus)) { - foreach (explode("\n", $gitStatus) as $statusLine) { - if (!str_contains($statusLine, ' config.yaml')) { - $this->stdErr->writeln('There are uncommitted changes in Git. Cannot proceed.'); - $this->stdErr->writeln('Use the --no-check-changes option to override this.'); - - return 1; - } - } - } - } - - if ($this->shell->commandExists('gh')) { - $process = $this->shell->executeCaptureProcess('gh auth status --show-token', null, true); - if (!preg_match('/Token: (\S+)/', $process->getOutput(), $matches)) { - $this->stdErr->writeln('Unable to obtain a GitHub token.'); - $this->stdErr->writeln('Log in to the GitHub CLI with: gh auth login'); - return 1; - } - $gitHubToken = $matches[1]; - } elseif (getenv('GITHUB_TOKEN')) { - $gitHubToken = getenv('GITHUB_TOKEN'); - } else { - $this->stdErr->writeln('The GITHUB_TOKEN environment variable should be set, or the gh CLI should be installed.'); - return 1; - } - - // Find the previous version number. - if ($lastVersion = Option::stringOrNull($input, 'last-version')) { - $lastVersion = ltrim($lastVersion, 'v'); - $lastTag = 'v' . $lastVersion; - - $this->stdErr->writeln('Last version number: ' . $lastVersion . ''); - } else { - $lastTag = $this->shell->mustExecute(['git', 'describe', '--tags', '--abbrev=0'], dir: CLI_ROOT); - $lastVersion = ltrim($lastTag, 'v'); - $this->stdErr->writeln('Last version number (from latest Git tag): ' . $lastVersion . ''); - } - - if (!Option::bool($input, 'no-check-changes') && !$this->hasGitDifferences($lastTag)) { - $this->stdErr->writeln('There are no changes since the last version.'); - - return 1; - } - - $allowLower = Option::bool($input, 'allow-lower'); - $validateNewVersion = function ($next) use ($lastVersion, $allowLower) { - if ($next === null) { - throw new \InvalidArgumentException('The new version is required.'); - } - if (!$allowLower && version_compare($next, $lastVersion, '<=')) { - throw new \InvalidArgumentException( - 'The new version number must be greater than ' . $lastVersion - . "\n" . 'Use --allow-lower to skip this check.', - ); - } - - return $next; - }; - - $versionUtil = new VersionUtil(); - - $newVersion = Argument::stringOrNull($input, 'version'); - if ($newVersion !== null) { - $validateNewVersion($newVersion); - } else { - if (!$input->isInteractive()) { - $this->stdErr->writeln('The version number is required in non-interactive mode.'); - - return 1; - } - - // Find a good default new version number. - $default = null; - $autoComplete = []; - if ($nextVersions = $versionUtil->nextVersions($lastVersion)) { - $default = reset($nextVersions); - $autoComplete = $nextVersions; - } - $newVersion = $this->questionHelper->askInput('New version number', $default, $autoComplete, $validateNewVersion); - } - - // Set up GitHub API connection details. - $http = new Client(); - $repo = Option::string($input, 'repo') ?: $this->config->getStr('application.github_repo'); - $repoUrl = implode('/', array_map('rawurlencode', explode('/', $repo))); - $repoApiUrl = 'https://api.github.com/repos/' . $repoUrl; - $repoGitUrl = 'git@github.com:' . $repo . '.git'; - - // Check if the chosen version number already exists as a release. - $tagName = 'v' . ltrim((string) $newVersion, 'v'); - $existsResponse = $http->get($repoApiUrl . '/releases/tags/' . $tagName, [ - 'headers' => [ - 'Authorization' => 'token ' . $gitHubToken, - 'Accept' => 'application/vnd.github.v3+json', - 'Content-Type' => 'application/json', - ], - 'http_errors' => false, - 'debug' => $output->isDebug(), - ]); - if ($existsResponse->getStatusCode() !== 404) { - if ($existsResponse->getStatusCode() >= 300) { - $this->stdErr->writeln('Failed to check for an existing release on GitHub.'); - - return 1; - } - $this->stdErr->writeln('A release tagged ' . $tagName . ' already exists on GitHub.'); - - return 1; - } - - // Validate the --phar option. - $pharFilename = Option::stringOrNull($input, 'phar'); - if ($pharFilename && !file_exists($pharFilename)) { - $this->stdErr->writeln('File not found: ' . $pharFilename . ''); - - return 1; - } - - // Check the manifest file for the right item to update. - $manifestFile = Option::stringOrNull($input, 'manifest') ?: CLI_ROOT . '/dist/manifest.json'; - $contents = file_get_contents($manifestFile); - if ($contents === false) { - throw new \RuntimeException('Manifest file not readable: ' . $manifestFile); - } - if (!is_writable($manifestFile)) { - throw new \RuntimeException('Manifest file not writable: ' . $manifestFile); - } - $this->stdErr->writeln('Checking manifest file: ' . $manifestFile); - $manifest = json_decode($contents, true); - if ($manifest === null && json_last_error()) { - throw new \RuntimeException('Failed to decode manifest file: ' . $manifestFile); - } - $latestItem = null; - $latestSameMajorItem = null; - foreach ($manifest as $key => $item) { - if ($latestItem === null || version_compare($item['version'], $latestItem['version'], '>')) { - $latestItem = &$manifest[$key]; - } - if ($versionUtil->majorVersion($item['version']) === $versionUtil->majorVersion($newVersion)) { - if ($latestSameMajorItem === null || version_compare($item['version'], $latestSameMajorItem['version'], '>')) { - $latestSameMajorItem = &$manifest[$key]; - } - } - } - $manifestItem = null; - switch (Option::string($input, 'manifest-mode')) { - case 'update-latest': - $manifestItem = &$latestItem; - break; - - case 'update-latest-matching': - $manifestItem = &$latestSameMajorItem; - break; - - case 'add': - break; - - default: - throw new \RuntimeException('Unrecognised --manifest-mode: ' . Option::string($input, 'manifest-mode')); - } - if ($manifestItem === null) { - array_unshift($manifest, []); - $manifestItem = &$manifest[0]; - } - - // Confirm the release changelog. - [$changelogFilename, $changelog] = $this->getReleaseChangelog($lastTag, $tagName); - $questionText = "\nChangelog:\n\n" . $changelog . "\n\nIs this changelog correct?"; - if (!$this->questionHelper->confirm($questionText)) { - $this->stdErr->writeln('Update or delete the file ' . $changelogFilename . ' and re-run this command.'); - - return 1; - } - - // Build a Phar file, if one doesn't already exist. - if (!$pharFilename) { - $pharFilename = sys_get_temp_dir() . '/' . $this->config->getStr('application.executable') . '.phar'; - $result = $this->subCommandRunner->run('self:build', [ - '--output' => $pharFilename, - '--yes' => true, - '--replace-version' => $newVersion, - ]); - if ($result !== 0) { - $this->stdErr->writeln('The build failed'); - - return $result; - } - } - - // Validate that the Phar file has the right version number. - $versionInPhar = $this->shell->mustExecute([ - (new PhpExecutableFinder())->find() ?: PHP_BINARY, - $pharFilename, - '--version', - ]); - if (!str_contains($versionInPhar, (string) $newVersion)) { - $this->stdErr->writeln('The file ' . $pharFilename . ' reports a different version: "' . $versionInPhar . '"'); - - return 1; - } - - // Construct the download URL (the public location of the Phar file). - $pharPublicFilename = $this->config->getStr('application.executable') . '.phar'; - $download_url = str_replace('{tag}', $tagName, $this->config->getWithDefault( - 'application.download_url', - 'https://github.com/' . $repoUrl . '/releases/download/{tag}/' . $pharPublicFilename, - )); - - // Construct the new manifest item details. - $manifestItem['version'] = $newVersion; - $manifestItem['sha1'] = sha1_file($pharFilename); - $manifestItem['sha256'] = hash_file('sha256', $pharFilename); - $manifestItem['name'] = basename($pharPublicFilename); - $manifestItem['url'] = $download_url; - $manifestItem['php']['min'] = '8.2'; - if (!empty($changelog)) { - // This is the newer release notes format. - $manifestItem['notes'][$newVersion] = $changelog; - - $this->stdErr->writeln('Changes:'); - $this->stdErr->writeln($changelog); - $this->stdErr->writeln(''); - } - $result = file_put_contents($manifestFile, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); - if ($result !== false) { - $this->stdErr->writeln('Updated manifest file: ' . $manifestFile); - } else { - $this->stdErr->writeln('Failed to update manifest file: ' . $manifestFile); - - return 1; - } - - // Commit any changes to Git. - $gitStatus = $this->git->execute(['status', '--porcelain'], CLI_ROOT, true); - if (is_string($gitStatus) && !empty($gitStatus)) { - $this->stdErr->writeln('Committing changes to Git'); - - $result = $this->shell->executeSimple('git commit --patch config.yaml dist/manifest.json --message ' . escapeshellarg('Release v' . $newVersion) . ' --edit', CLI_ROOT); - if ($result !== 0) { - return $result; - } - } - - // Tag the current commit. - $this->stdErr->writeln('Creating tag ' . $tagName . ''); - $this->git->execute(['tag', '--force', $tagName], CLI_ROOT, true); - - // Push to GitHub. - if (!$this->questionHelper->confirm('Push changes to ' . $releaseBranch . ' branch on ' . $repoGitUrl . '?')) { - return 1; - } - $this->shell->execute(['git', 'push', $repoGitUrl, 'HEAD:' . $releaseBranch], CLI_ROOT, true); - $this->shell->execute(['git', 'push', '--force', $repoGitUrl, $tagName], CLI_ROOT, true); - - // Upload a release to GitHub. - $lastReleasePublicUrl = 'https://github.com/' . $repoUrl . '/releases/' . $lastTag; - $releaseDescription = sprintf('Changes since [%s](%s):', $lastTag, $lastReleasePublicUrl); - if (!empty($changelog)) { - $releaseDescription .= "\n\n" . $changelog; - } - $releaseDescription .= "\n\n" . 'https://github.com/' . $repoUrl . '/compare/' . $lastTag . '...' . $tagName; - $releaseDescription .= "\n\n" . sprintf('SHA-256 checksum for `%s`:', $pharPublicFilename) - . "\n" . sprintf('`%s`', hash_file('sha256', $pharFilename)); - $this->stdErr->writeln(''); - $this->stdErr->writeln('Creating new release ' . $tagName . ' on GitHub'); - $this->stdErr->writeln('Release description:'); - $this->stdErr->writeln(preg_replace('/^/m', ' ', $releaseDescription)); - $this->stdErr->writeln(''); - $http = new Client(); - $response = $http->post($repoApiUrl . '/releases', [ - 'headers' => [ - 'Authorization' => 'token ' . $gitHubToken, - 'Accept' => 'application/vnd.github.v3+json', - 'Content-Type' => 'application/json', - ], - 'json' => [ - 'tag_name' => $tagName, - 'name' => $tagName, - 'body' => $releaseDescription, - 'draft' => true, - 'prerelease' => $versionUtil->isPreRelease($newVersion), - ], - 'debug' => $output->isDebug(), - ]); - $release = (array) Utils::jsonDecode((string) $response->getBody(), true); - $releaseUrl = $repoApiUrl . '/releases/' . $release['id']; - $uploadUrl = preg_replace('/\{.+?}/', '', (string) $release['upload_url']); - - // Upload the Phar to the GitHub release. - $this->stdErr->writeln('Uploading the Phar file to the release'); - $fileResource = fopen($pharFilename, 'r'); - if (!$fileResource) { - throw new \RuntimeException('Failed to open file for reading: ' . $fileResource); - } - $http->post($uploadUrl . '?name=' . rawurldecode($pharPublicFilename), [ - 'headers' => [ - 'Authorization' => 'token ' . $gitHubToken, - 'Accept' => 'application/vnd.github.v3+json', - 'Content-Type' => 'application/octet-stream', - ], - 'body' => $fileResource, - 'debug' => $output->isDebug(), - ]); - - // Mark the GitHub release as published. - $this->stdErr->writeln('Publishing the release'); - $http->patch($releaseUrl, [ - 'headers' => [ - 'Authorization' => 'token ' . $gitHubToken, - 'Accept' => 'application/vnd.github.v3+json', - 'Content-Type' => 'application/json', - ], - 'json' => ['draft' => false], - 'debug' => $output->isDebug(), - ]); - $this->stdErr->writeln(''); - $this->stdErr->writeln('Release successfully published'); - $this->stdErr->writeln('https://github.com/' . $repoUrl . '/releases/latest'); - - return 0; - } - - /** - * @param string $lastVersionTag The tag corresponding to the last version. - * @param string $newVersionTag The tag corresponding to the new version being released. - * - * @return string[] The filename and the current changelog. - */ - private function getReleaseChangelog(string $lastVersionTag, string $newVersionTag): array - { - $filename = CLI_ROOT . '/release-changelog-' . $newVersionTag . '.md'; - if (file_exists($filename)) { - $mTime = filemtime($filename); - $lastVersionDate = $this->getTagDate($lastVersionTag); - if (!$lastVersionDate || !$mTime || $mTime > $lastVersionDate) { - $contents = file_get_contents($filename); - if ($contents === false) { - throw new \RuntimeException('Failed to read file: ' . $filename); - } - $changelog = trim($contents); - } - } - if (empty($changelog)) { - $changelog = $this->getGitChangelog($lastVersionTag); - (new Filesystem())->dumpFile($filename, $changelog); - } - - return [$filename, $changelog]; - } - - /** - * Returns the commit date associated with a tag. - * - * @param string $tagName - * - * @return int|false - */ - private function getTagDate(string $tagName): int|false - { - $date = $this->git->execute(['log', '-1', '--format=%aI', 'refs/tags/' . $tagName]); - - return is_string($date) ? strtotime(trim($date)) : false; - } - - /** - * Checks if there are relevant Git differences since the last version. - * - * @param string $since - * - * @return bool - */ - private function hasGitDifferences(string $since): bool - { - $stat = $this->git->execute(['diff', '--numstat', $since . '...HEAD'], CLI_ROOT, true); - if (!is_string($stat)) { - return false; - } - - foreach (explode("\n", trim($stat)) as $line) { - // Exclude config.yaml and dist/manifest.json from the check. - if (!str_contains($line, ' config.yaml') && !str_contains($line, ' dist/manifest.json')) { - return true; - } - } - - return false; - } - - /** - * @param string $since - * - * @return string - */ - private function getGitChangelog(string $since): string - { - $changelog = $this->git->execute([ - 'log', - '--pretty=tformat:* %s%n%b', - '--no-merges', - '--invert-grep', - '--grep=(Release v|\[skip changelog\])', - '--perl-regexp', - '--regexp-ignore-case', - $since . '...HEAD', - ], CLI_ROOT); - if (!is_string($changelog)) { - return ''; - } - - $changelog = preg_replace('/^[^*\n]/m', ' $0', $changelog); - $changelog = preg_replace('/\n+\*/', "\n*", (string) $changelog); - $changelog = trim((string) $changelog); - - return $changelog; - } -} diff --git a/legacy/src/Command/Self/SelfStatsCommand.php b/legacy/src/Command/Self/SelfStatsCommand.php deleted file mode 100644 index 5580f5d14..000000000 --- a/legacy/src/Command/Self/SelfStatsCommand.php +++ /dev/null @@ -1,87 +0,0 @@ - */ - private array $tableHeader = ['Release', 'Date', 'Asset', 'Downloads']; - - public function __construct(private readonly Config $config, private readonly PropertyFormatter $propertyFormatter, private readonly Table $table) - { - parent::__construct(); - } - - protected function configure(): void - { - $this - ->addOption('page', 'p', InputOption::VALUE_REQUIRED, 'Page number', 1) - ->addOption('count', 'c', InputOption::VALUE_REQUIRED, 'Results per page (max: 100)', 20); - Table::configureInput($this->getDefinition(), $this->tableHeader); - PropertyFormatter::configureInput($this->getDefinition()); - } - - public function isEnabled(): bool - { - return $this->config->has('application.github_repo'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $repo = $this->config->getStr('application.github_repo'); - $repoUrl = implode('/', array_map('rawurlencode', explode('/', $repo))); - $response = (new Client()) - ->get('https://api.github.com/repos/' . $repoUrl . '/releases', [ - 'headers' => [ - 'Accept' => 'application/vnd.github.v3+json', - ], - 'query' => [ - 'page' => Option::int($input, 'page'), - 'per_page' => Option::int($input, 'count'), - ], - ]); - $releases = (array) Utils::jsonDecode((string) $response->getBody(), true); - - if (empty($releases)) { - $this->stdErr->writeln('No releases found.'); - - return 1; - } - $rows = []; - foreach ($releases as $release) { - $row = []; - $row[] = $release['name']; - $time = !empty($release['published_at']) ? $release['published_at'] : $release['created_at']; - $row[] = $this->propertyFormatter->format($time, 'created_at'); - if (!empty($release['assets'])) { - foreach ($release['assets'] as $asset) { - $row[] = $asset['name']; - $row[] = $this->propertyFormatter->format($asset['download_count']); - break; - } - } - $rows[] = $row; - } - - $this->table->render($rows, $this->tableHeader); - - return 0; - } -} diff --git a/legacy/src/Command/Self/SelfUpdateCommand.php b/legacy/src/Command/Self/SelfUpdateCommand.php deleted file mode 100644 index e7b37a568..000000000 --- a/legacy/src/Command/Self/SelfUpdateCommand.php +++ /dev/null @@ -1,56 +0,0 @@ -addOption('no-major', null, InputOption::VALUE_NONE, 'Only update between minor or patch versions') - ->addOption('unstable', null, InputOption::VALUE_NONE, 'Update to a new unstable version, if available') - ->addOption('manifest', null, InputOption::VALUE_REQUIRED, 'Override the manifest file location') - ->addOption('current-version', null, InputOption::VALUE_REQUIRED, 'Override the current version') - ->addOption('timeout', null, InputOption::VALUE_REQUIRED, 'A timeout for the version check', 30); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $manifestUrl = Option::stringOrNull($input, 'manifest') ?: $this->config->getStr('application.manifest_url'); - $currentVersion = Option::stringOrNull($input, 'current-version') ?: $this->config->getVersion(); - $this->selfUpdater->setAllowMajor(!Option::bool($input, 'no-major')); - $this->selfUpdater->setAllowUnstable(Option::bool($input, 'unstable')); - $this->selfUpdater->setTimeout(Option::int($input, 'timeout')); - - $result = $this->selfUpdater->update($manifestUrl, $currentVersion); - if ($result === '') { - return 0; - } - if ($result === false) { - return 1; - } - - // Phar cannot load more classes after the update has occurred. So to - // avoid errors from classes loaded after this (e.g. - // ConsoleTerminateEvent), we exit directly now. - exit(0); - } -} diff --git a/legacy/src/Rector/InjectCommandServicesRector.php b/legacy/src/Rector/InjectCommandServicesRector.php index 9e0fff3b0..6a4e4ebab 100644 --- a/legacy/src/Rector/InjectCommandServicesRector.php +++ b/legacy/src/Rector/InjectCommandServicesRector.php @@ -26,7 +26,6 @@ use Platformsh\Cli\Service\RemoteEnvVars; use Platformsh\Cli\Service\Relationships; use Platformsh\Cli\Service\Rsync; -use Platformsh\Cli\Service\SelfUpdater; use Platformsh\Cli\Service\Shell; use Platformsh\Cli\Service\Ssh; use Platformsh\Cli\Service\SshConfig; @@ -75,7 +74,6 @@ class InjectCommandServicesRector extends AbstractRector 'remote_env_vars' => RemoteEnvVars::class, 'relationships' => Relationships::class, 'rsync' => Rsync::class, - 'self_updater' => SelfUpdater::class, 'shell' => Shell::class, 'ssh' => Ssh::class, 'ssh_config' => SshConfig::class, diff --git a/legacy/src/SelfUpdate/ManifestStrategy.php b/legacy/src/SelfUpdate/ManifestStrategy.php deleted file mode 100644 index cfa177232..000000000 --- a/legacy/src/SelfUpdate/ManifestStrategy.php +++ /dev/null @@ -1,283 +0,0 @@ -|null */ - private ?array $manifest = null; - - /** @var array>|null */ - private ?array $availableVersions = null; - - private const REQUIRED_KEYS = ['sha256', 'version', 'url']; - - private int $manifestTimeout = 10; - - private int $downloadTimeout = 60; - - private bool $ignorePhpReq = false; - - /** @var array */ - private array $streamContextOptions = []; - - /** - * ManifestStrategy constructor. - * - * @param string $localVersion The local version. - * @param string $manifestUrl The URL to a JSON manifest file. The - * manifest contains an array of objects, each - * containing a 'version', 'sha256', and 'url'. - * @param bool $allowMajor Whether to allow updating between major - * versions. - * @param bool $allowUnstable Whether to allow updating to an unstable - * version. Ignored if $localVersion is unstable - * and there are no new stable versions. - */ - public function __construct(private readonly string $localVersion, private readonly string $manifestUrl, private readonly bool $allowMajor = false, private readonly bool $allowUnstable = false) {} - - /** - * @param array $opts - */ - public function setStreamContextOptions(array $opts): void - { - $this->streamContextOptions = $opts; - } - - public function setManifestTimeout(int $manifestTimeout): void - { - $this->manifestTimeout = $manifestTimeout; - } - - /** - * {@inheritdoc} - */ - public function getCurrentLocalVersion(Updater $updater): string - { - return $this->localVersion; - } - - /** - * {@inheritdoc} - */ - public function getCurrentRemoteVersion(Updater $updater): bool|string - { - $versions = array_keys($this->getAvailableVersions()); - if (!$this->allowMajor) { - $versions = $this->filterByLocalMajorVersion($versions); - } - if (!$this->ignorePhpReq) { - $versions = $this->filterByPhpVersion($versions); - } - - $versionParser = new VersionParser($versions); - - $mostRecent = $versionParser->getMostRecentStable(); - - // Look for unstable updates if explicitly allowed, or if the local - // version is already unstable and there is no new stable version. - if ($this->allowUnstable - || ($versionParser->isUnstable($this->localVersion) - && version_compare($mostRecent, $this->localVersion, '<'))) { - $mostRecent = $versionParser->getMostRecentAll(); - } - - return version_compare($mostRecent, $this->localVersion, '>') ? $mostRecent : false; - } - - /** - * Finds update/upgrade notes for the new remote version. - * - * @return array - */ - public function getUpdateNotesByVersion(string $currentVersion, string $targetVersion): array - { - $notes = []; - foreach ($this->getAvailableVersions() as $version => $item) { - if (isset($item['notes']) && \version_compare($version, $currentVersion, '>') && \version_compare($version, $targetVersion, '<=')) { - if (is_array($item['notes'])) { - foreach ($item['notes'] as $notesVersion => $notesString) { - if (\version_compare($notesVersion, $currentVersion, '>') && \version_compare($notesVersion, $targetVersion, '<=')) { - $notes[$notesVersion] = $notesString; - } - } - } else { - $notes[$version] = $item['notes']; - } - } - } - uksort($notes, fn($a, $b): int => \version_compare($a, $b)); - return $notes; - } - - /** - * {@inheritdoc} - */ - public function download(Updater $updater): void - { - $versionInfo = $this->getRemoteVersionInfo($updater); - - // A relative download URL is treated as relative to the manifest URL. - $url = $versionInfo['url']; - if (!str_contains((string) $url, '//') && str_contains($this->manifestUrl, '//')) { - $removePath = (string) parse_url($this->manifestUrl, PHP_URL_PATH); - $url = str_replace($removePath, '/' . ltrim((string) $url, '/'), $this->manifestUrl); - } - - $opts = $this->streamContextOptions; - $opts['http']['timeout'] = $this->downloadTimeout; - $fileContents = file_get_contents($url, false, stream_context_create($opts)); - if ($fileContents === false) { - throw new HttpRequestException(sprintf('Failed to download file from URL: %s', $versionInfo['url'])); - } - - $tmpFilename = $updater->getTempPharFile(); - if (file_put_contents($tmpFilename, $fileContents) === false) { - throw new \RuntimeException(sprintf('Failed to write file: %s', $tmpFilename)); - } - - $tmpSha = hash_file('sha256', $tmpFilename); - if ($tmpSha !== $versionInfo['sha256']) { - unlink($tmpFilename); - throw new \RuntimeException( - sprintf( - 'SHA-256 verification failed: expected %s, actual %s', - $versionInfo['sha256'], - $tmpSha, - ), - ); - } - } - - /** - * Get available versions to update to. - * - * @return array - * }> - * An array keyed by the version name, whose elements are arrays - * containing version information. - */ - private function getAvailableVersions(): array - { - if (!isset($this->availableVersions)) { - $this->availableVersions = []; - foreach ($this->getManifest() as $key => $item) { - if ($missing = array_diff(self::REQUIRED_KEYS, array_keys($item))) { - throw new \RuntimeException(sprintf( - 'Manifest item %s missing required key(s): %s', - $key, - implode(',', $missing), - )); - } - $this->availableVersions[$item['version']] = $item; - } - } - - return $this->availableVersions; - } - - /** - * Get version information for the latest remote version. - * - * @param Updater $updater - * - * @return array - */ - private function getRemoteVersionInfo(Updater $updater): array - { - $version = $this->getCurrentRemoteVersion($updater); - if ($version === false) { - throw new \RuntimeException('No remote versions found'); - } - $versionInfo = $this->getAvailableVersions(); - if (!isset($versionInfo[$version])) { - throw new \RuntimeException(sprintf('Failed to find manifest item for version %s', $version)); - } - - return $versionInfo[$version]; - } - - /** - * Downloads and decodes the JSON manifest file. - * - * @return array - */ - private function getManifest(): array - { - if (!isset($this->manifest)) { - $opts = $this->streamContextOptions; - $opts['http']['timeout'] = $this->manifestTimeout; - $manifestContents = file_get_contents($this->manifestUrl, false, stream_context_create($opts)); - if ($manifestContents === false) { - throw new \RuntimeException(sprintf('Failed to download manifest: %s', $this->manifestUrl)); - } - - $this->manifest = (array) json_decode($manifestContents, true); - if (json_last_error() !== JSON_ERROR_NONE) { - throw new JsonParsingException( - 'Error parsing manifest file' - . (function_exists('json_last_error_msg') ? ': ' . json_last_error_msg() : ''), - ); - } - } - - return $this->manifest; - } - - /** - * Filter a list of versions to those that match the current local version. - * - * @param string[] $versions - * - * @return string[] - */ - private function filterByLocalMajorVersion(array $versions): array - { - [$localMajorVersion, ] = explode('.', $this->localVersion, 2); - - return array_filter($versions, function ($version) use ($localMajorVersion): bool { - [$majorVersion, ] = explode('.', $version, 2); - return $majorVersion === $localMajorVersion; - }); - } - - /** - * Filter a list of versions to those that allow the current PHP version. - * - * @param string[] $versions - * - * @return string[] - */ - private function filterByPhpVersion(array $versions): array - { - $versionInfo = $this->getAvailableVersions(); - - return array_filter($versions, function ($version) use ($versionInfo): bool { - if (isset($versionInfo[$version]['php']['min']) - && version_compare(PHP_VERSION, $versionInfo[$version]['php']['min'], '<')) { - return false; - } elseif (isset($versionInfo[$version]['php']['max']) - && version_compare(PHP_VERSION, $versionInfo[$version]['php']['max'], '>')) { - return false; - } - - return true; - }); - } -} diff --git a/legacy/src/Service/SelfUpdateChecker.php b/legacy/src/Service/SelfUpdateChecker.php deleted file mode 100644 index dbaede7e8..000000000 --- a/legacy/src/Service/SelfUpdateChecker.php +++ /dev/null @@ -1,129 +0,0 @@ -stdErr = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; - } - - public function checkUpdates(): void - { - // Avoid checking more than once in this process. - if (self::$checkedUpdates) { - return; - } - self::$checkedUpdates = true; - - // Check that the Phar extension is available. - if (!extension_loaded('Phar')) { - return; - } - - // Get the filename of the Phar, or stop if this instance of the CLI is - // not a Phar. - $pharFilename = \Phar::running(false); - if (!$pharFilename) { - return; - } - - // Check if the file and its containing directory are writable. - if (!is_writable($pharFilename) || !is_writable(dirname($pharFilename))) { - return; - } - - // Check if updates are configured. - if (!$this->config->get('updates.check')) { - return; - } - - // Determine an embargo time, after which updates can be checked. - $timestamp = time(); - $embargoTime = $timestamp - $this->config->getInt('updates.check_interval'); - - // Stop if updates were last checked after the embargo time. - if ($this->state->get('updates.last_checked') > $embargoTime) { - return; - } - - // Stop if the Phar was updated after the embargo time. - if (filemtime($pharFilename) > $embargoTime) { - return; - } - - // Ensure classes are auto-loaded if they may be needed after the - // update. - $currentVersion = $this->config->getVersion(); - - $this->selfUpdater->setAllowMajor(true); - $this->selfUpdater->setTimeout(5); - - try { - $newVersion = $this->selfUpdater->update(null, $currentVersion); - } catch (\RuntimeException $e) { - if (str_contains($e->getMessage(), 'Failed to download')) { - $this->stdErr->writeln('' . $e->getMessage() . ''); - $newVersion = false; - } else { - throw $e; - } - } - - $this->state->set('updates.last_checked', $timestamp); - - if ($newVersion === '') { - // No update was available. - return; - } - - if ($newVersion !== false) { - // Update succeeded. Continue (based on a few conditions). - $exitCode = 0; - [$currentMajorVersion, ] = explode('.', $currentVersion, 2); - [$newMajorVersion, ] = explode('.', $newVersion, 2); - if ($newMajorVersion === $currentMajorVersion - && $this->input instanceof ArgvInput - && is_executable($pharFilename)) { - $originalCommand = $this->input->__toString(); - if (empty($originalCommand)) { - $exitCode = $this->shell->executeSimple(escapeshellarg($pharFilename)); - exit($exitCode); - } - - $questionText = "\n" - . 'Original command: ' . $originalCommand . '' - . "\n\n" . 'Continue?'; - if ($this->questionHelper->confirm($questionText)) { - $this->stdErr->writeln(''); - $exitCode = $this->shell->executeSimple(escapeshellarg($pharFilename) . ' ' . $originalCommand); - } - } - exit($exitCode); - } - - // Automatic update failed. - // Error messages will already have been printed, and the original - // command can continue. - $this->stdErr->writeln(''); - } -} diff --git a/legacy/src/Service/SelfUpdater.php b/legacy/src/Service/SelfUpdater.php deleted file mode 100644 index a27053cc1..000000000 --- a/legacy/src/Service/SelfUpdater.php +++ /dev/null @@ -1,168 +0,0 @@ -stdErr = $this->output instanceof ConsoleOutputInterface - ? $this->output->getErrorOutput() - : $this->output; - } - - /** - * Set the timeout for the version check. - * - * @param int $timeout - * The timeout in seconds. - */ - public function setTimeout(int $timeout): void - { - $this->timeout = $timeout; - } - - /** - * Set the updater to allow unstable versions. - * - * @param bool $allowUnstable - */ - public function setAllowUnstable(bool $allowUnstable = true): void - { - $this->allowUnstable = $allowUnstable; - } - - /** - * Set the updater to allow major version updates. - * - * @param bool $allowMajor - */ - public function setAllowMajor(bool $allowMajor = true): void - { - $this->allowMajor = $allowMajor; - } - - /** - * Run the update. - * - * @param string|null $manifestUrl - * @param string|null $currentVersion - * - * @return false|string - * The new version number, or an empty string if there was no update, or false on error. - */ - public function update(?string $manifestUrl = null, ?string $currentVersion = null): string|false - { - $currentVersion = $currentVersion ?: $this->config->getVersion(); - $manifestUrl = $manifestUrl ?: $this->config->getStr('application.manifest_url'); - $applicationName = $this->config->getStr('application.name'); - if (!extension_loaded('Phar') || !($localPhar = \Phar::running(false))) { - $this->stdErr->writeln(sprintf( - 'This instance of the %s was not installed as a Phar archive.', - $applicationName, - )); - - // Instructions for users who are running a global Composer install. - if (defined('CLI_ROOT') && file_exists(CLI_ROOT . '/../../autoload.php')) { - $this->stdErr->writeln("Update using:\n\n composer global update"); - if ($this->config->has('application.package_name')) { - $this->stdErr->writeln("\nOr you can switch to a Phar install (recommended):\n"); - $this->stdErr->writeln(" composer global remove " . $this->config->getStr('application.package_name')); - $this->stdErr->writeln(" curl -sS " . $this->config->getStr('application.installer_url') . " | php\n"); - } else { - $this->stdErr->writeln("\nOr you can switch to a Phar install (recommended)\n"); - } - } - - return false; - } - - $this->stdErr->writeln(sprintf( - 'Checking for %s updates (current version: %s)', - $applicationName, - $currentVersion, - )); - - if (!is_writable($localPhar)) { - $this->stdErr->writeln('Cannot update as the Phar file is not writable: ' . $localPhar); - - return false; - } - if (!is_writable(dirname($localPhar))) { - $this->stdErr->writeln('Cannot update as the directory is not writable: ' . dirname($localPhar)); - - return false; - } - - $updater = new Updater($localPhar, false); - $strategy = new ManifestStrategy(ltrim($currentVersion, 'v'), $manifestUrl, $this->allowMajor, $this->allowUnstable); - $strategy->setManifestTimeout($this->timeout); - $strategy->setStreamContextOptions($this->config->getStreamContextOptions()); - $updater->setStrategyObject($strategy); - - if (!$updater->hasUpdate()) { - $this->stdErr->writeln('No updates found'); - return ''; - } - - $newVersionString = $updater->getNewVersion(); - - // Some dev versions cannot be compared against other version numbers, - // so do not check for release notes in that case. - $currentIsDev = str_starts_with($currentVersion, 'dev-'); - - if (!$currentIsDev && ($notes = $strategy->getUpdateNotesByVersion($currentVersion, $newVersionString))) { - $this->stdErr->writeln(''); - $this->stdErr->writeln(sprintf('Version %s is available. Release notes:', $newVersionString)); - foreach ($notes as $version => $notesStr) { - if (\count($notes) > 1) { - $this->stdErr->writeln('' . $version . ':'); - } - $this->stdErr->writeln(preg_replace('/^/m', ' ', $notesStr)); - $this->stdErr->writeln(''); - } - } - - if (!$this->questionHelper->confirm(sprintf('Update to version %s?', $newVersionString))) { - return false; - } - - $this->stdErr->writeln(sprintf('Updating to version %s', $newVersionString)); - - $updater->update(); - - $this->stdErr->writeln(sprintf( - 'The %s has been successfully updated to version %s', - $applicationName, - $newVersionString, - )); - - return $newVersionString; - } -} diff --git a/legacy/tests/InstallerTest.php b/legacy/tests/InstallerTest.php deleted file mode 100644 index 9f0125948..000000000 --- a/legacy/tests/InstallerTest.php +++ /dev/null @@ -1,100 +0,0 @@ -assertEquals( - [ - ['version' => '1.0.0'], - ['version' => '1.0.1'], - ['version' => '1.0.2-beta'], - ], - $resolver->findInstallableVersions([ - ['version' => '1.0.0'], - ['version' => '1.0.1'], - ['version' => '1.0.2-beta'], - ['version' => '1.0.3-dev'], - ], PHP_VERSION, ['beta']), - ); - $this->assertEquals( - [ - ['version' => '1.0.0-stable'], - ['version' => '1.0.1'], - ['version' => '1.0.2-beta'], - ], - $resolver->findInstallableVersions([ - ['version' => '1.0.0-stable'], - ['version' => '1.0.1'], - ['version' => '1.0.2-beta'], - ['version' => '1.0.3-dev'], - ], PHP_VERSION, ['stable', 'beta']), - ); - } - - public function testFindInstallableVersionsChecksFoMinPhp(): void - { - $this->assertEmpty((new VersionResolver())->findInstallableVersions([ - [ - 'version' => '1.0.0', - 'php' => ['min' => '5.5.9'], - ], - ], '5.5.0')); - } - - public function testFindLatestVersionWithMax(): void - { - $this->assertEquals('3.0.0', (new VersionResolver())->findLatestVersion([ - ['version' => '1.0.0'], - ['version' => '2.0.0'], - ['version' => '3.0.0'], - ['version' => '3.0.1'], - ], '', '3.0.0')['version']); - } - - public function testFindLatestVersionWithMin(): void - { - $this->assertEquals('3.0.1', (new VersionResolver())->findLatestVersion([ - ['version' => '1.0.0'], - ['version' => '3.0.1'], - ['version' => '2.0.0'], - ['version' => '3.0.0'], - ], '2.0')['version']); - - $this->expectException(\RuntimeException::class); - (new VersionResolver())->findLatestVersion([ - ['version' => '1.0.0'], - ['version' => '3.0.1'], - ['version' => '2.0.0'], - ['version' => '3.0.0'], - ], 'v3.1'); - } - - public function testGetOption(): void - { - $method = new \ReflectionMethod(Installer::class, 'getOption'); - $method->setAccessible(true); - $args = ['--min', '1.2.3']; - $this->assertEquals('1.2.3', $method->invoke(new Installer($args), 'min')); - $args = ['--max', '2.3.4']; - $this->assertEquals('', $method->invoke(new Installer($args), 'min')); - $this->assertEquals('2.3.4', $method->invoke(new Installer($args), 'max')); - $args = ['--max=2.0.0']; - $this->assertEquals('2.0.0', $method->invoke(new Installer($args), 'max')); - } - -} From 3d15ed80eb9d384c87866c0aade03427254bd3ad Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Sun, 27 Sep 2026 23:42:49 +0100 Subject: [PATCH 2/5] chore(legacy): remove unused VersionUtil VersionUtil was only used by self:release and the self-update code. Also note why the wrapper still sets UPDATES_CHECK=0 for the PHP CLI: wrapper processes spawned from it (e.g. ssh-cert:load via SSH config) inherit the variable. Co-Authored-By: Claude Opus 5.5 --- internal/legacy/legacy.go | 1 + legacy/src/Util/VersionUtil.php | 57 --------------------------- legacy/tests/Util/VersionUtilTest.php | 41 ------------------- 3 files changed, 1 insertion(+), 98 deletions(-) delete mode 100644 legacy/src/Util/VersionUtil.php delete mode 100644 legacy/tests/Util/VersionUtilTest.php diff --git a/internal/legacy/legacy.go b/internal/legacy/legacy.go index e9e457174..7f387f8f9 100644 --- a/internal/legacy/legacy.go +++ b/internal/legacy/legacy.go @@ -141,6 +141,7 @@ func (c *CLIWrapper) Exec(ctx context.Context, args ...string) error { cmd.Env = append( cmd.Env, "CLI_CONFIG_FILE="+filepath.Join(cacheDir, configBasename), + // Stops update checks in wrapper processes the PHP CLI spawns, e.g. via SSH config. envPrefix+"UPDATES_CHECK=0", envPrefix+"MIGRATE_CHECK=0", envPrefix+"APPLICATION_PROMPT_SELF_INSTALL=0", diff --git a/legacy/src/Util/VersionUtil.php b/legacy/src/Util/VersionUtil.php deleted file mode 100644 index ff074df6d..000000000 --- a/legacy/src/Util/VersionUtil.php +++ /dev/null @@ -1,57 +0,0 @@ -assertEquals(['1.0.1', '1.1.0', '2.0.0'], $util->nextVersions('1.0.0')); - $this->assertEquals(['1.0.2', '1.1.0', '2.0.0'], $util->nextVersions('1.0.1')); - $this->assertEquals(['2.0.3-beta', '2.1.0-beta', '3.0.0-beta'], $util->nextVersions('2.0.2-beta')); - } - - public function testMajorVersion(): void - { - $util = new VersionUtil(); - $this->assertEquals(1, $util->majorVersion('1.0.0')); - $this->assertEquals(10, $util->majorVersion('10.1')); - $this->assertEquals(2, $util->majorVersion('2.x')); - $this->assertEquals(2, $util->majorVersion('2.1.1')); - $this->assertEquals(2, $util->majorVersion('2.10.10')); - $this->assertEquals(3, $util->majorVersion('3.1.0-beta')); - } - - public function testIsPreRelease(): void - { - $util = new VersionUtil(); - $this->assertEquals(1, $util->majorVersion('1.0.0')); - $this->assertEquals(10, $util->majorVersion('10.1')); - $this->assertEquals(2, $util->majorVersion('2.x')); - $this->assertEquals(2, $util->majorVersion('2.1.1')); - $this->assertEquals(2, $util->majorVersion('2.10.10')); - $this->assertEquals(3, $util->majorVersion('3.1.0-beta')); - } -} From d36538837d95e36c05e8e0161973d51653918717 Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Sun, 27 Sep 2026 23:48:09 +0100 Subject: [PATCH 3/5] docs: rewrite legacy/README.md and move configuration docs to the root legacy/README.md described the archived standalone CLI (install, upgrade and usage for platformsh/cli). It is now a short developer note for the PHP layer, and absorbs the still-relevant parts of legacy/CONTRIBUTING.md, which pointed at the archived repository. The user config file and environment variable docs move to a new Configuration section in the root README, using the UPSUN_CLI_ prefix. This also fixes the SSH certificate variable name, which is AUTO_LOAD_SSH_CERT, not SSH_AUTO_LOAD_CERT. Co-Authored-By: Claude Opus 5.5 --- README.md | 35 +++++++++++++- legacy/CONTRIBUTING.md | 62 ------------------------ legacy/README.md | 107 ++++++++++------------------------------- 3 files changed, 58 insertions(+), 146 deletions(-) delete mode 100644 legacy/CONTRIBUTING.md diff --git a/README.md b/README.md index a567858ea..d2eb9d1aa 100644 --- a/README.md +++ b/README.md @@ -177,8 +177,39 @@ control it: `` is one of `homebrew`, `scoop`, `npm`, `package`, or `script`. Use `package` to silence the notice when the OS manages updates. -(For the `platform` command, the prefix is `PLATFORMSH_CLI_` instead of -`UPSUN_CLI_`.) +## Configuration + +You can override configuration in the user config file, +`~/.upsun-cli/config.yaml`. The available keys are in +[legacy/config-defaults.yaml](legacy/config-defaults.yaml). + +Environment variables include: + +- `UPSUN_CLI_TOKEN`: an API token, for non-interactive use such as CI. An API + token can act as the account that created it, so use a separate machine + account to limit its access. Interactively, prefer `upsun auth:api-token-login`. +- `UPSUN_CLI_DEBUG=1`: enable debug output. This can print HTTP request details, + including access tokens. +- `UPSUN_CLI_DEFAULT_TIMEOUT`: the timeout in seconds for most API requests + (default 30). +- `UPSUN_CLI_DISABLE_CACHE=1`: disable caching. To clear the cache once, run + `upsun clear-cache`. +- `UPSUN_CLI_HOME`: override the home directory, which contains `.upsun-cli`. +- `UPSUN_CLI_NO_INTERACTION=1`: disable interaction, like `--no-interaction`. + This skips confirmation questions. +- `UPSUN_CLI_SESSION_ID`: switch user session (default `default`). See also + `upsun session:switch`. +- `UPSUN_CLI_AUTO_LOAD_SSH_CERT=0`: disable automatically loading an SSH + certificate when running login or SSH commands. +- `UPSUN_CLI_SHELL_CONFIG_FILE`: the shell config file that `self:install` + writes to (an absolute path). Set it to an empty string to skip writing one. +- `UPSUN_CLI_REPORT_DEPRECATIONS=1`: show PHP deprecation notices in debug mode + (`-vvv`). +- `NO_COLOR=1` or `CLICOLOR_FORCE=0`/`1`: turn colors off or force them on. +- `http_proxy` or `https_proxy`: use an HTTP proxy. + +The `UPSUN_CLI_` prefix and the `.upsun-cli` directory come from the CLI's +embedded config, so other builds of the CLI may use different ones. ## Building diff --git a/legacy/CONTRIBUTING.md b/legacy/CONTRIBUTING.md deleted file mode 100644 index 15c036039..000000000 --- a/legacy/CONTRIBUTING.md +++ /dev/null @@ -1,62 +0,0 @@ -# Contributing - -Development of the Legacy CLI happens in public in the [GitHub repository](https://github.com/platformsh/legacy-cli). - -Issues and pull requests submitted via GitHub are very welcome. - -The principal Upsun CLI repository is: https://github.com/platformsh/cli - -## Developing locally - -If you clone this repository locally, you can build it with: - -```sh -composer install -``` - -Run the CLI from the local source with: - -```sh -./bin/platform -``` - -Run tests with: - -```sh -./scripts/test/unit.sh -``` - -## Developing in a docker container - -If you don't have PHP installed locally or for other reasons want to do development on the CLI inside a docker -container, follow this procedure: - -Create a `.env` file based on the default one - -```sh -cp .env-dist .env -``` - -You should ensure that the UID and GUI used inside the container matches your local user to avoid file permission problems. -To get the UID and GID for local user, run: - -```sh -id -``` - -If your `uid` and `gid` is not `1000`, then alter `USER_ID` and `GROUP_ID` in `.env` accordingly. - - -Next, build and start your container - -```sh -docker-compose up -d -``` - -Attach to the running container - -```sh -docker-compose exec cli bash -``` - -Now you may run the steps mentioned in ["Developing locally"](#Developing-locally) inside the container. diff --git a/legacy/README.md b/legacy/README.md index 30ece5922..a8075ca73 100644 --- a/legacy/README.md +++ b/legacy/README.md @@ -1,98 +1,41 @@ -The **Legacy** CLI is the legacy version of the command-line interface for [Upsun (formerly Platform.sh)](https://upsun.com). +# Legacy PHP CLI -For the **current Upsun CLI**, check [this repository](https://github.com/platformsh/cli). +This directory contains the PHP layer of the Upsun CLI. It is built into a +phar and embedded in the Go wrapper, which runs it with its own PHP binary. +Commands not implemented in Go are passed to it. -## Install +It was merged in from the archived +[platformsh/legacy-cli](https://github.com/platformsh/legacy-cli) repository. +Changes are now made here. -To install the CLI, use either [Homebrew](https://brew.sh/) (on Linux, macOS, or the Windows Subsystem for Linux) or [Scoop](https://scoop.sh/) (on Windows): +See the [root README](../README.md) for installing, configuring and building +the CLI. -### HomeBrew +## Development -```console -brew install upsun/tap/platformsh-cli -``` - -### Scoop +Install dependencies: -```console -scoop bucket add platformsh https://github.com/platformsh/homebrew-tap.git -scoop install platform +```sh +composer install ``` -### Manual installation - -For manual installation, you can also [download the latest binaries](https://github.com/platformsh/cli/releases/latest). - -## Upgrade +Run the PHP CLI from source: -Upgrade using the same tool: - -### HomeBrew - -```console -brew upgrade platformsh-cli +```sh +./bin/platform ``` -### Scoop +Run linters (php-cs-fixer and PHPStan) and unit tests: -```console -scoop update platform +```sh +make lint +make test ``` -## Usage - -You can run this CLI in your shell by typing `platform`. - - platform - -Use the 'list' command to get a list of available options and commands: - - platform list - -## Authentication - -There are two ways to authenticate: - -1. The recommended way is `platform login`, which lets you log in via a web browser, including via third-party providers such as Google, GitHub, GitLab and Bitbucket. - -2. If using a browser is not possible, use an [API token](https://docs.upsun.com/anchors/fixed/cli/api-token/). - - An interactive command is available for this: `platform auth:api-token-login` - - For non-interactive uses such as scripts or CI systems, set the API token in an environment variable named `PLATFORMSH_CLI_TOKEN`. This can be insecure if not handled properly, although it is appropriate for systems such as CircleCI, Jenkins and GitLab. - - *_Warning_*: An API token can act as the account that created it, with no restrictions. Use a separate machine account to limit the token's access. - -## Customization - -You can configure the CLI via the user configuration file `~/.platformsh/config.yaml`. - -The possible keys that can be overridden are in the [config-defaults.yaml](/config-defaults.yaml) and [config.yaml](/config.yaml) files. - -Other customization is available via environment variables, including: - -* `PLATFORMSH_CLI_DEBUG`: set to 1 to enable debugging. _Warning_: this could print HTTP request information in the terminal, including sensitive access tokens. -* `PLATFORMSH_CLI_DEFAULT_TIMEOUT`: the timeout (in seconds) for most individual API requests. The default is 30. -* `PLATFORMSH_CLI_DISABLE_CACHE`: set to 1 to disable caching -* `PLATFORMSH_CLI_HOME`: override the home directory (inside which the .platformsh directory is stored) -* `PLATFORMSH_CLI_NO_COLOR`: set to 1 to disable colors in output -* `PLATFORMSH_CLI_NO_INTERACTION`: set to 1 to disable interaction (useful for scripting). Equivalent to the `--no-interaction` command-line option. _Warning_: this will bypass any confirmation questions. -* `PLATFORMSH_CLI_SESSION_ID`: change user session (default 'default'). The `session:switch` command (beta) is now available as an alternative. -* `PLATFORMSH_CLI_SHELL_CONFIG_FILE`: specify the shell configuration file that the installer should write to (as an absolute path). If not set, a file such as `~/.bashrc` will be chosen automatically. Set this to an empty string to disable writing to a shell config file. -* `PLATFORMSH_CLI_TOKEN`: an API token. *_Warning_*: An API token can act as the account that created it, with no restrictions. Use a separate machine account to limit the token's access. Additionally, storing a secret in an environment variable can be insecure. It may be better to use the `auth:api-token-login` command. The environment variable is preferable on CI systems like Jenkins and GitLab. -* `PLATFORMSH_CLI_SSH_AUTO_LOAD_CERT`: set to 0 to disable automatic loading of an SSH certificate when running login or SSH commands -* `PLATFORMSH_CLI_REPORT_DEPRECATIONS`: set to 1 to enable PHP deprecation notices (suppressed by default). They will only be displayed in debug mode (`-vvv`). -* `CLICOLOR_FORCE`: set to 1 or 0 to force colorized output on or off, respectively -* `http_proxy` or `https_proxy`: specify an HTTP proxy - -## Known issues - -### Caching - -The CLI caches details of your projects and their environments, and some other -information. These caches could become out-of-date. You can clear caches with -the command `platform clear-cache` (or `platform cc` for short). +After changing services or commands, delete the cached container +(`make clean`). -## Contributing +To build the embedded phar, run `make single` from the repository root. -See [CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute to the CLI. +A Docker development environment is available: copy `.env-dist` to `.env`, +then run `docker-compose up -d` and `docker-compose exec cli bash`. From 3d7b18436636210c55571e40b9a80a931b058c4b Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Sun, 27 Sep 2026 23:56:17 +0100 Subject: [PATCH 4/5] chore(legacy): require PHP 8.4 The wrapper embeds PHP 8.4, so raise the minimum and the Composer platform version to match, and update the development Dockerfile. Locked dependency versions are unchanged. Co-Authored-By: Claude Opus 5.5 --- legacy/Dockerfile | 2 +- legacy/composer.json | 4 ++-- legacy/composer.lock | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/legacy/Dockerfile b/legacy/Dockerfile index a7eca6230..8bbce9457 100644 --- a/legacy/Dockerfile +++ b/legacy/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.2-cli +FROM php:8.4-cli ARG USER_ID ARG GROUP_ID diff --git a/legacy/composer.json b/legacy/composer.json index 5fc0c4ec1..3380afe7a 100644 --- a/legacy/composer.json +++ b/legacy/composer.json @@ -3,7 +3,7 @@ "description": "Platform.sh CLI", "license": "MIT", "require": { - "php": ">=8.2", + "php": ">=8.4", "doctrine/cache": "~1.5", "guzzlehttp/guzzle": "^7", "platformsh/console-form": "^1@beta", @@ -58,7 +58,7 @@ ], "config": { "platform": { - "php": "8.2" + "php": "8.4" } }, "scripts": { diff --git a/legacy/composer.lock b/legacy/composer.lock index 44f2f467c..d21089611 100644 --- a/legacy/composer.lock +++ b/legacy/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "363e7e9f7b7eecf9b0943dbe9b5f515a", + "content-hash": "7b82e9a5b71609c891c5da0ec002ec94", "packages": [ { "name": "cocur/slugify", @@ -6202,12 +6202,12 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=8.2", + "php": ">=8.4", "ext-json": "*" }, "platform-dev": {}, "platform-overrides": { - "php": "8.2" + "php": "8.4" }, "plugin-api-version": "2.9.0" } From f06556ae49a9d310785ba6e266d7d876da06f6f2 Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Mon, 28 Sep 2026 10:13:41 +0100 Subject: [PATCH 5/5] chore(legacy): stop passing UPDATES_CHECK=0 to the PHP CLI It only disabled the PHP CLI's own update checker, which has been removed. The wrapper's update check is passive in the processes the PHP CLI may spawn (e.g. ssh-cert:load via SSH config): it prints only a notice, and not in quiet mode. Co-Authored-By: Claude Opus 5.5 --- docs/design/update-message-install-detection.md | 4 ++-- internal/legacy/legacy.go | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/design/update-message-install-detection.md b/docs/design/update-message-install-detection.md index e9ffcb9c6..fcd603b83 100644 --- a/docs/design/update-message-install-detection.md +++ b/docs/design/update-message-install-detection.md @@ -282,8 +282,8 @@ Update now? [Y/n] a week. Because the prompt and update run in `PersistentPreRun`, and the legacy PHP layer -is already invoked with `UPDATES_CHECK=0` (`internal/legacy/legacy.go:139`), -there is no double-notification when delegating to PHP commands. +has no update check of its own, there is no double-notification when delegating +to PHP commands. ### The bash installer's destination (Phase 3, optional) diff --git a/internal/legacy/legacy.go b/internal/legacy/legacy.go index 7f387f8f9..1e01fb91c 100644 --- a/internal/legacy/legacy.go +++ b/internal/legacy/legacy.go @@ -141,8 +141,6 @@ func (c *CLIWrapper) Exec(ctx context.Context, args ...string) error { cmd.Env = append( cmd.Env, "CLI_CONFIG_FILE="+filepath.Join(cacheDir, configBasename), - // Stops update checks in wrapper processes the PHP CLI spawns, e.g. via SSH config. - envPrefix+"UPDATES_CHECK=0", envPrefix+"MIGRATE_CHECK=0", envPrefix+"APPLICATION_PROMPT_SELF_INSTALL=0", envPrefix+"WRAPPED=1",