From a700f9283b49ee31f22fed93001f8171870d4ef0 Mon Sep 17 00:00:00 2001 From: Dane Powell Date: Tue, 27 Aug 2024 10:50:22 -0700 Subject: [PATCH] CLI-979: Consolidate update-check logic (#1774) * CLI-979: Consolidate update-check logic * a * remove guzzle cache * fix tests * upstream * test updates * refactor * fix updates * fix tests * update self-update --- .env.example | 3 + .gitignore | 1 + CONTRIBUTING.md | 10 +- bin/acli | 7 +- composer.json | 3 +- composer.lock | 290 ++++-------------- config/prod/services.yml | 13 +- src/Command/Acsf/AcsfCommandFactory.php | 4 + src/Command/Api/ApiCommandFactory.php | 4 + src/Command/App/LogTailCommand.php | 4 +- src/Command/CommandBase.php | 84 +---- src/Command/Ide/IdeCreateCommand.php | 4 +- src/Command/Pull/PullCommandBase.php | 4 +- tests/phpunit/src/Application/KernelTest.php | 2 +- tests/phpunit/src/ApplicationTestBase.php | 1 + tests/phpunit/src/CommandTestBase.php | 20 +- .../src/Commands/Acsf/AcsfApiCommandTest.php | 1 + .../src/Commands/App/LogTailCommandTest.php | 1 + .../src/Commands/Ide/IdeCreateCommandTest.php | 1 + .../src/Commands/Pull/PullCodeCommandTest.php | 1 + .../src/Commands/Pull/PullCommandTest.php | 1 + .../Commands/Pull/PullDatabaseCommandTest.php | 1 + .../Commands/Pull/PullFilesCommandTest.php | 1 + .../src/Commands/UpdateCommandTest.php | 43 ++- tests/phpunit/src/TestBase.php | 5 + 25 files changed, 146 insertions(+), 363 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..c9e997a9b --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +BUGSNAG_KEY=abcd1234 +AMPLITUDE_KEY=acbd1234 +ACLI_VERSION=1.0.0 diff --git a/.gitignore b/.gitignore index 44caf9627..7f5ec4468 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ drupal cx-api-spec gardener +.env // Artifacts from mutation testing *.cache diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d31b2a302..a19a3b232 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,19 +66,11 @@ Be sure to validate and test your code locally using the provided Composer test To test changes in production mode, build and run `acli.phar` using this process. The _build-release_ stage of [`.github/workflows/ci.yml`](.github/workflows/ci.yml) follows a similar process. 1. Install Composer production dependencies: `composer install --no-dev --optimize-autoloader` -2. Create a `.env` file with Bugsnag and Amplitude keys +2. Populate `.env`: `cp .env.example .env` 3. Clear and rebuild your Symfony caches: `./bin/acli ckc && ./bin/acli cc` 4. Install Box (only need to do this once): `composer box-install` 5. Compile phar: `composer box-compile` -### Testing the `update` command - -Any changes to the `acli update` command should be manually tested using the following steps: - -1. Replace `@package_version@` on this line with `1.0.0` or any older version string: https://github.com/acquia/cli/blob/v1.0.0/bin/acli#L84 -1. Build acli.phar as described above. -1. Now test: `./var/acli.phar self:update` - ### Writing tests New code should be covered at 100% (or as close to it as reasonably possible) by PHPUnit tests. It should also minimize the number of escaped mutants (as close to 0% as reasonably possible), which will appear as annotations on your PR after unit tests run. diff --git a/bin/acli b/bin/acli index 5c8c5c150..de300a07c 100755 --- a/bin/acli +++ b/bin/acli @@ -23,6 +23,7 @@ use Acquia\Cli\Exception\AcquiaCliException; use Acquia\Cli\Helpers\LocalMachineHelper; use Dotenv\Dotenv; use SelfUpdate\SelfUpdateCommand; +use SelfUpdate\SelfUpdateManager; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Filesystem\Filesystem; @@ -93,14 +94,14 @@ if (!getenv('ACLI_REPO_ROOT')) { $application = $container->get(Application::class); /** @var OutputInterface $output */ $output = $container->get(OutputInterface::class); -$application->setName('Acquia CLI'); -$application->setVersion(getenv('ACLI_VERSION')); /** @var ApiCommandHelper $helper */ $helper = $container->get(ApiCommandHelper::class); $application->addCommands($helper->getApiCommands( __DIR__ . '/../assets/acquia-spec.json', 'api', $container->get(ApiCommandFactory::class))); $application->addCommands($helper->getApiCommands( __DIR__ . '/../assets/acsf-spec.json', 'acsf', $container->get(AcsfCommandFactory::class))); try { - $application->add(new SelfUpdateCommand($application->getName(), $application->getVersion(), 'acquia/cli')); + /** @var SelfUpdateManager $selfUpdateManager*/ + $selfUpdateManager = $container->get(SelfUpdateManager::class); + $application->add(new SelfUpdateCommand($selfUpdateManager)); } catch (\UnexpectedValueException) { // Do nothing for development builds. diff --git a/composer.json b/composer.json index b184cb4f7..8d37e819a 100644 --- a/composer.json +++ b/composer.json @@ -17,12 +17,11 @@ "acquia/drupal-environment-detector": "^1.2.0", "bugsnag/bugsnag": "^3.0", "composer/semver": "^3.3", - "consolidation/self-update": "dev-main as 2.1.0", + "consolidation/self-update": "3.0.0-alpha1 as 2.1.0", "dflydev/dot-access-data": "^3", "grasmash/expander": "^3", "guzzlehttp/guzzle": "^7.4", "http-interop/http-factory-guzzle": "^1.0", - "kevinrob/guzzle-cache-middleware": "^5", "laminas/laminas-validator": "^2.20.0", "league/csv": "^9.8", "loophp/phposinfo": "^1.7.2", diff --git a/composer.lock b/composer.lock index 5c9f416a4..4c9913bb4 100644 --- a/composer.lock +++ b/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": "c49bd75c4ca413cd16cd85fe278e6352", + "content-hash": "31ee2d035e880509553850fb78e03a91", "packages": [ { "name": "acquia/drupal-environment-detector", @@ -407,26 +407,27 @@ }, { "name": "consolidation/self-update", - "version": "dev-main", + "version": "3.0.0-alpha1", "source": { "type": "git", "url": "https://github.com/consolidation/self-update.git", - "reference": "e3cc75a07c36b68bd5ab72ead23a375529fba71f" + "reference": "3476c5ef074e0953ad5c235a66f65b1c271d0faa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/self-update/zipball/e3cc75a07c36b68bd5ab72ead23a375529fba71f", - "reference": "e3cc75a07c36b68bd5ab72ead23a375529fba71f", + "url": "https://api.github.com/repos/consolidation/self-update/zipball/3476c5ef074e0953ad5c235a66f65b1c271d0faa", + "reference": "3476c5ef074e0953ad5c235a66f65b1c271d0faa", "shasum": "" }, "require": { "composer/semver": "^3.2", + "guzzlehttp/guzzle": "^7.9", + "kevinrob/guzzle-cache-middleware": "^5.1", "php": "^8.1", + "symfony/cache": "^5.4 || ^6.4 || ^7", "symfony/console": "^5.4 || ^6.4 || ^7", - "symfony/filesystem": "^5.4 || ^6.4 || ^7", - "symfony/http-client": "^5.4 || ^6.4 || ^7" + "symfony/filesystem": "^5.4 || ^6.4 || ^7" }, - "default-branch": true, "bin": [ "scripts/release" ], @@ -458,9 +459,9 @@ "description": "Provides a self:update command for Symfony Console applications.", "support": { "issues": "https://github.com/consolidation/self-update/issues", - "source": "https://github.com/consolidation/self-update/tree/main" + "source": "https://github.com/consolidation/self-update/tree/3.0.0-alpha1" }, - "time": "2024-07-18T18:42:07+00:00" + "time": "2024-08-27T17:12:45+00:00" }, { "name": "dflydev/dot-access-data", @@ -4727,177 +4728,6 @@ ], "time": "2024-04-27T10:22:22+00:00" }, - { - "name": "symfony/http-client", - "version": "v6.4.10", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client.git", - "reference": "b5e498f763e0bf5eed8dcd946e50a3b3f71d4ded" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/b5e498f763e0bf5eed8dcd946e50a3b3f71d4ded", - "reference": "b5e498f763e0bf5eed8dcd946e50a3b3f71d4ded", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "^3.4.1", - "symfony/service-contracts": "^2.5|^3" - }, - "conflict": { - "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.3" - }, - "provide": { - "php-http/async-client-implementation": "*", - "php-http/client-implementation": "*", - "psr/http-client-implementation": "1.0", - "symfony/http-client-implementation": "3.0" - }, - "require-dev": { - "amphp/amp": "^2.5", - "amphp/http-client": "^4.2.1", - "amphp/http-tunnel": "^1.0", - "amphp/socket": "^1.1", - "guzzlehttp/promises": "^1.4|^2.0", - "nyholm/psr7": "^1.0", - "php-http/httplug": "^1.0|^2.0", - "psr/http-client": "^1.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", - "homepage": "https://symfony.com", - "keywords": [ - "http" - ], - "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.10" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-07-15T09:26:24+00:00" - }, - { - "name": "symfony/http-client-contracts", - "version": "v3.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "20414d96f391677bf80078aa55baece78b82647d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d", - "reference": "20414d96f391677bf80078aa55baece78b82647d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to HTTP clients", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-04-18T09:32:20+00:00" - }, { "name": "symfony/http-foundation", "version": "v6.4.10", @@ -7852,48 +7682,48 @@ }, { "name": "composer/composer", - "version": "2.7.7", + "version": "2.7.8", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "291942978f39435cf904d33739f98d7d4eca7b23" + "reference": "a2edd4e4414c17008ab585e0c62574fdb644ebfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/291942978f39435cf904d33739f98d7d4eca7b23", - "reference": "291942978f39435cf904d33739f98d7d4eca7b23", + "url": "https://api.github.com/repos/composer/composer/zipball/a2edd4e4414c17008ab585e0c62574fdb644ebfc", + "reference": "a2edd4e4414c17008ab585e0c62574fdb644ebfc", "shasum": "" }, "require": { - "composer/ca-bundle": "^1.0", + "composer/ca-bundle": "^1.5", "composer/class-map-generator": "^1.3.3", "composer/metadata-minifier": "^1.0", - "composer/pcre": "^2.1 || ^3.1", + "composer/pcre": "^2.2 || ^3.2", "composer/semver": "^3.3", "composer/spdx-licenses": "^1.5.7", "composer/xdebug-handler": "^2.0.2 || ^3.0.3", - "justinrainbow/json-schema": "^5.2.11", + "justinrainbow/json-schema": "^5.3", "php": "^7.2.5 || ^8.0", "psr/log": "^1.0 || ^2.0 || ^3.0", - "react/promise": "^2.8 || ^3", + "react/promise": "^3.2", "seld/jsonlint": "^1.4", "seld/phar-utils": "^1.2", "seld/signal-handler": "^2.0", - "symfony/console": "^5.4.11 || ^6.0.11 || ^7", - "symfony/filesystem": "^5.4 || ^6.0 || ^7", - "symfony/finder": "^5.4 || ^6.0 || ^7", + "symfony/console": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/filesystem": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/finder": "^5.4.35 || ^6.3.12 || ^7.0.3", "symfony/polyfill-php73": "^1.24", "symfony/polyfill-php80": "^1.24", "symfony/polyfill-php81": "^1.24", - "symfony/process": "^5.4 || ^6.0 || ^7" + "symfony/process": "^5.4.35 || ^6.3.12 || ^7.0.3" }, "require-dev": { - "phpstan/phpstan": "^1.11.0", + "phpstan/phpstan": "^1.11.8", "phpstan/phpstan-deprecation-rules": "^1.2.0", "phpstan/phpstan-phpunit": "^1.4.0", "phpstan/phpstan-strict-rules": "^1.6.0", "phpstan/phpstan-symfony": "^1.4.0", - "symfony/phpunit-bridge": "^6.4.1 || ^7.0.1" + "symfony/phpunit-bridge": "^6.4.3 || ^7.0.1" }, "suggest": { "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", @@ -7946,7 +7776,7 @@ "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", "security": "https://github.com/composer/composer/security/policy", - "source": "https://github.com/composer/composer/tree/2.7.7" + "source": "https://github.com/composer/composer/tree/2.7.8" }, "funding": [ { @@ -7962,7 +7792,7 @@ "type": "tidelift" } ], - "time": "2024-06-10T20:11:12+00:00" + "time": "2024-08-22T13:28:36+00:00" }, { "name": "composer/metadata-minifier", @@ -8035,26 +7865,26 @@ }, { "name": "composer/pcre", - "version": "3.2.0", + "version": "3.3.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90" + "reference": "1637e067347a0c40bbb1e3cd786b20dcab556a81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/ea4ab6f9580a4fd221e0418f2c357cdd39102a90", - "reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90", + "url": "https://api.github.com/repos/composer/pcre/zipball/1637e067347a0c40bbb1e3cd786b20dcab556a81", + "reference": "1637e067347a0c40bbb1e3cd786b20dcab556a81", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, "conflict": { - "phpstan/phpstan": "<1.11.8" + "phpstan/phpstan": "<1.11.10" }, "require-dev": { - "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan": "^1.11.10", "phpstan/phpstan-strict-rules": "^1.1", "phpunit/phpunit": "^8 || ^9" }, @@ -8094,7 +7924,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.2.0" + "source": "https://github.com/composer/pcre/tree/3.3.0" }, "funding": [ { @@ -8110,7 +7940,7 @@ "type": "tidelift" } ], - "time": "2024-07-25T09:36:02+00:00" + "time": "2024-08-19T19:43:53+00:00" }, { "name": "composer/spdx-licenses", @@ -10813,16 +10643,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.11.11", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3" + "reference": "384af967d35b2162f69526c7276acadce534d0e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/707c2aed5d8d0075666e673a5e71440c1d01a5a3", - "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/384af967d35b2162f69526c7276acadce534d0e1", + "reference": "384af967d35b2162f69526c7276acadce534d0e1", "shasum": "" }, "require": { @@ -10867,7 +10697,7 @@ "type": "github" } ], - "time": "2024-08-19T14:37:29+00:00" + "time": "2024-08-27T09:18:05+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -10918,35 +10748,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.31", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -10955,7 +10785,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -10984,7 +10814,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -10992,7 +10822,7 @@ "type": "github" } ], - "time": "2024-03-02T06:37:42+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", @@ -13405,15 +13235,13 @@ "aliases": [ { "package": "consolidation/self-update", - "version": "dev-main", + "version": "3.0.0.0-alpha1", "alias": "2.1.0", "alias_normalized": "2.1.0.0" } ], "minimum-stability": "dev", - "stability-flags": { - "consolidation/self-update": 20 - }, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { diff --git a/config/prod/services.yml b/config/prod/services.yml index 2ba0c2337..a1ec16643 100644 --- a/config/prod/services.yml +++ b/config/prod/services.yml @@ -1,6 +1,8 @@ --- parameters: env(ACLI_REPO_ROOT): "%kernel.project_dir%" + app.name: 'Acquia CLI' + app.version: "%env(ACLI_VERSION)%" app.data_dir: "%env(ACLI_HOME)%" app.project_dir: "%env(ACLI_REPO_ROOT)%" app.ssh_dir: "%env(HOME)%/.ssh" @@ -131,7 +133,10 @@ services: Acquia\Cli\ApiCredentialsInterface: '@acsf.credentials' # Symfony services. - Acquia\Cli\Application: ~ + Acquia\Cli\Application: + arguments: + $name: '%app.name%' + $version: '%app.version%' Symfony\Component\Console\Input\ArgvInput: ~ Symfony\Component\Console\Input\InputInterface: alias: Symfony\Component\Console\Input\ArgvInput @@ -148,3 +153,9 @@ services: # Guzzle service. GuzzleHttp\Client: ~ + + SelfUpdate\SelfUpdateManager: + arguments: + $applicationName: '%app.name%' + $currentVersion: '%app.version%' + $gitHubRepository: 'acquia/cli' diff --git a/src/Command/Acsf/AcsfCommandFactory.php b/src/Command/Acsf/AcsfCommandFactory.php index 9031d4590..dc412c360 100644 --- a/src/Command/Acsf/AcsfCommandFactory.php +++ b/src/Command/Acsf/AcsfCommandFactory.php @@ -14,6 +14,7 @@ use Acquia\Cli\Helpers\SshHelper; use Acquia\Cli\Helpers\TelemetryHelper; use Psr\Log\LoggerInterface; +use SelfUpdate\SelfUpdateManager; class AcsfCommandFactory implements CommandFactoryInterface { @@ -28,6 +29,7 @@ public function __construct( private SshHelper $sshHelper, private string $sshDir, private LoggerInterface $logger, + private SelfUpdateManager $selfUpdateManager, ) { } @@ -44,6 +46,7 @@ public function createCommand(): ApiBaseCommand $this->sshHelper, $this->sshDir, $this->logger, + $this->selfUpdateManager, ); } @@ -60,6 +63,7 @@ public function createListCommand(): AcsfListCommand $this->sshHelper, $this->sshDir, $this->logger, + $this->selfUpdateManager, ); } } diff --git a/src/Command/Api/ApiCommandFactory.php b/src/Command/Api/ApiCommandFactory.php index ed226121f..2e45f64a5 100644 --- a/src/Command/Api/ApiCommandFactory.php +++ b/src/Command/Api/ApiCommandFactory.php @@ -13,6 +13,7 @@ use Acquia\Cli\Helpers\SshHelper; use Acquia\Cli\Helpers\TelemetryHelper; use Psr\Log\LoggerInterface; +use SelfUpdate\SelfUpdateManager; class ApiCommandFactory implements CommandFactoryInterface { @@ -27,6 +28,7 @@ public function __construct( private SshHelper $sshHelper, private string $sshDir, private LoggerInterface $logger, + private SelfUpdateManager $selfUpdateManager, ) { } @@ -43,6 +45,7 @@ public function createCommand(): ApiBaseCommand $this->sshHelper, $this->sshDir, $this->logger, + $this->selfUpdateManager, ); } @@ -59,6 +62,7 @@ public function createListCommand(): ApiListCommand $this->sshHelper, $this->sshDir, $this->logger, + $this->selfUpdateManager, ); } } diff --git a/src/Command/App/LogTailCommand.php b/src/Command/App/LogTailCommand.php index 9109c1aff..feaf60209 100644 --- a/src/Command/App/LogTailCommand.php +++ b/src/Command/App/LogTailCommand.php @@ -16,6 +16,7 @@ use AcquiaCloudApi\Endpoints\Logs; use AcquiaLogstream\LogstreamManager; use Psr\Log\LoggerInterface; +use SelfUpdate\SelfUpdateManager; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -39,9 +40,10 @@ public function __construct( public SshHelper $sshHelper, protected string $sshDir, LoggerInterface $logger, + public selfUpdateManager $selfUpdateManager, protected LogstreamManager $logstreamManager, ) { - parent::__construct($this->localMachineHelper, $this->datastoreCloud, $this->datastoreAcli, $this->cloudCredentials, $this->telemetryHelper, $this->projectDir, $this->cloudApiClientService, $this->sshHelper, $this->sshDir, $logger); + parent::__construct($this->localMachineHelper, $this->datastoreCloud, $this->datastoreAcli, $this->cloudCredentials, $this->telemetryHelper, $this->projectDir, $this->cloudApiClientService, $this->sshHelper, $this->sshDir, $logger, $this->selfUpdateManager); } protected function configure(): void diff --git a/src/Command/CommandBase.php b/src/Command/CommandBase.php index a8f56a816..2f4be8628 100644 --- a/src/Command/CommandBase.php +++ b/src/Command/CommandBase.php @@ -41,21 +41,15 @@ use AcquiaLogstream\LogstreamManager; use ArrayObject; use Closure; -use Composer\Semver\Comparator; -use Composer\Semver\VersionParser; use Exception; -use GuzzleHttp\HandlerStack; use JsonException; -use Kevinrob\GuzzleCache\CacheMiddleware; -use Kevinrob\GuzzleCache\Storage\Psr6CacheStorage; -use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy; use loophp\phposinfo\OsInfo; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerInterface; use Safe\Exceptions\FilesystemException; +use SelfUpdate\SelfUpdateManager; use stdClass; -use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\FormatterHelper; use Symfony\Component\Console\Helper\ProgressBar; @@ -103,8 +97,6 @@ abstract class CommandBase extends Command implements LoggerAwareInterface protected bool $drushHasActiveDatabaseConnection; - protected \GuzzleHttp\Client $updateClient; - public function __construct( public LocalMachineHelper $localMachineHelper, protected CloudDataStore $datastoreCloud, @@ -116,6 +108,7 @@ public function __construct( public SshHelper $sshHelper, protected string $sshDir, LoggerInterface $logger, + public selfUpdateManager $selfUpdateManager, ) { $this->logger = $logger; $this->setLocalDbPassword(); @@ -1203,9 +1196,12 @@ public function checkForNewVersion(): bool|string if (AcquiaDrupalEnvironmentDetector::isAhIdeEnv()) { return false; } + if ($this->getApplication()->getVersion() === 'UNKNOWN') { + return false; + } try { - if ($latest = $this->hasUpdate()) { - return $latest; + if (!$this->selfUpdateManager->isUpToDate()) { + return $this->selfUpdateManager->getLatestReleaseFromGithub()['tag_name']; } } catch (Exception) { $this->logger->debug("Could not determine if Acquia CLI has a new version available."); @@ -1213,72 +1209,6 @@ public function checkForNewVersion(): bool|string return false; } - /** - * Check if an update is available. - * - * @todo unify with consolidation/self-update and support unstable channels - */ - protected function hasUpdate(): bool|string - { - $versionParser = new VersionParser(); - // Fail fast on development builds (throw UnexpectedValueException). - $currentVersion = $versionParser->normalize($this->getApplication() - ->getVersion()); - $client = $this->getUpdateClient(); - $response = $client->get('https://api.github.com/repos/acquia/cli/releases'); - if ($response->getStatusCode() !== 200) { - $this->logger->debug('Encountered ' . $response->getStatusCode() . ' error when attempting to check for new ACLI releases on GitHub: ' . $response->getReasonPhrase()); - return false; - } - - $releases = json_decode((string) $response->getBody(), false, 512, JSON_THROW_ON_ERROR); - if (!isset($releases[0])) { - $this->logger->debug('No releases found at GitHub repository acquia/cli'); - return false; - } - - foreach ($releases as $release) { - if (!$release->prerelease) { - /** - * @var string $version - */ - $version = $release->tag_name; - $versionStability = VersionParser::parseStability($version); - $versionIsNewer = Comparator::greaterThan($versionParser->normalize($version), $currentVersion); - if ($versionStability === 'stable' && $versionIsNewer) { - return $version; - } - return false; - } - } - return false; - } - - public function setUpdateClient(\GuzzleHttp\Client $client): void - { - $this->updateClient = $client; - } - - public function getUpdateClient(): \GuzzleHttp\Client - { - if (!isset($this->updateClient)) { - $stack = HandlerStack::create(); - $stack->push( - new CacheMiddleware( - new PrivateCacheStrategy( - new Psr6CacheStorage( - new FilesystemAdapter('acli') - ) - ) - ), - 'cache' - ); - $client = new \GuzzleHttp\Client(['handler' => $stack]); - $this->setUpdateClient($client); - } - return $this->updateClient; - } - protected function fillMissingRequiredApplicationUuid(InputInterface $input, OutputInterface $output): void { if ( diff --git a/src/Command/Ide/IdeCreateCommand.php b/src/Command/Ide/IdeCreateCommand.php index 5aa5b3956..4d9903c05 100644 --- a/src/Command/Ide/IdeCreateCommand.php +++ b/src/Command/Ide/IdeCreateCommand.php @@ -20,6 +20,7 @@ use AcquiaCloudApi\Response\OperationResponse; use GuzzleHttp\Client; use Psr\Log\LoggerInterface; +use SelfUpdate\SelfUpdateManager; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -46,9 +47,10 @@ public function __construct( public SshHelper $sshHelper, protected string $sshDir, LoggerInterface $logger, + public selfUpdateManager $selfUpdateManager, protected Client $httpClient ) { - parent::__construct($this->localMachineHelper, $this->datastoreCloud, $this->datastoreAcli, $this->cloudCredentials, $this->telemetryHelper, $this->projectDir, $this->cloudApiClientService, $this->sshHelper, $this->sshDir, $logger); + parent::__construct($this->localMachineHelper, $this->datastoreCloud, $this->datastoreAcli, $this->cloudCredentials, $this->telemetryHelper, $this->projectDir, $this->cloudApiClientService, $this->sshHelper, $this->sshDir, $logger, $this->selfUpdateManager); } protected function configure(): void diff --git a/src/Command/Pull/PullCommandBase.php b/src/Command/Pull/PullCommandBase.php index 985166d98..3d7a424ff 100644 --- a/src/Command/Pull/PullCommandBase.php +++ b/src/Command/Pull/PullCommandBase.php @@ -29,6 +29,7 @@ use Psr\Http\Message\UriInterface; use Psr\Log\LoggerInterface; use React\EventLoop\Loop; +use SelfUpdate\SelfUpdateManager; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; @@ -58,9 +59,10 @@ public function __construct( public SshHelper $sshHelper, protected string $sshDir, LoggerInterface $logger, + public selfUpdateManager $selfUpdateManager, protected \GuzzleHttp\Client $httpClient ) { - parent::__construct($this->localMachineHelper, $this->datastoreCloud, $this->datastoreAcli, $this->cloudCredentials, $this->telemetryHelper, $this->projectDir, $this->cloudApiClientService, $this->sshHelper, $this->sshDir, $logger); + parent::__construct($this->localMachineHelper, $this->datastoreCloud, $this->datastoreAcli, $this->cloudCredentials, $this->telemetryHelper, $this->projectDir, $this->cloudApiClientService, $this->sshHelper, $this->sshDir, $logger, $this->selfUpdateManager); } /** diff --git a/tests/phpunit/src/Application/KernelTest.php b/tests/phpunit/src/Application/KernelTest.php index 0faec8699..2d31f55ba 100644 --- a/tests/phpunit/src/Application/KernelTest.php +++ b/tests/phpunit/src/Application/KernelTest.php @@ -26,7 +26,7 @@ public function testRun(): void private function getStart(): string { return <<endVersion is available", $this->getDisplay()); } public function testBadResponseFailsSilently(): void { - $this->setUpdateClient(403); - $this->application->setVersion('2.8.4'); + $this->application->setVersion($this->startVersion); + $this->mockSelfUpdateCommand(true); $this->executeCommand(); self::assertEquals(0, $this->getStatusCode()); - self::assertStringNotContainsString('Acquia CLI 2.8.5 is available', $this->getDisplay()); + self::assertStringNotContainsString("Acquia CLI $this->endVersion is available", $this->getDisplay()); } - public function testNetworkErrorFailsSilently(): void + /** + * @throws \GuzzleHttp\Exception\GuzzleException + */ + private function mockSelfUpdateCommand(bool $exception = false): void { - $guzzleClient = $this->prophet->prophesize(Client::class); - $guzzleClient->get('https://api.github.com/repos/acquia/cli/releases') - ->willThrow(RequestException::class); - $this->command->setUpdateClient($guzzleClient->reveal()); - $this->application->setVersion('2.8.4.9999s'); - $this->executeCommand(); - self::assertEquals(0, $this->getStatusCode()); - self::assertStringNotContainsString('Acquia CLI 2.8.5 is available', $this->getDisplay()); + $selfUpdateManagerProphecy = $this->prophet->prophesize(SelfUpdateManager::class); + if ($exception) { + $selfUpdateManagerProphecy->isUpToDate()->willThrow(new Exception())->shouldBeCalled(); + } else { + $selfUpdateManagerProphecy->isUpToDate() + ->willReturn(false) + ->shouldBeCalled(); + $selfUpdateManagerProphecy->getLatestReleaseFromGithub() + ->willReturn(['tag_name' => $this->endVersion]) + ->shouldBeCalled(); + } + $this->command->selfUpdateManager = $selfUpdateManagerProphecy->reveal(); } } diff --git a/tests/phpunit/src/TestBase.php b/tests/phpunit/src/TestBase.php index a2bfd0a28..1183c4997 100644 --- a/tests/phpunit/src/TestBase.php +++ b/tests/phpunit/src/TestBase.php @@ -30,6 +30,7 @@ use Prophecy\Prophet; use Psr\Http\Message\StreamInterface; use RuntimeException; +use SelfUpdate\SelfUpdateManager; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\PhpArrayAdapter; use Symfony\Component\Cache\CacheItem; @@ -110,6 +111,8 @@ abstract class TestBase extends TestCase protected ConsoleLogger $logger; + public selfUpdateManager $selfUpdateManager; + protected string $passphraseFilepath = '~/.passphrase'; protected vfsStreamDirectory $vfsRoot; @@ -175,6 +178,7 @@ protected function setUp(): void $this->consoleOutput = new ConsoleOutput(); $this->setClientProphecies(); $this->setIo(); + $this->selfUpdateManager = $this->prophet->prophesize(SelfUpdateManager::class)->reveal(); $this->vfsRoot = vfsStream::setup(); $this->projectDir = vfsStream::newDirectory('project') @@ -351,6 +355,7 @@ protected function injectCommand(string $commandName): Command $this->sshHelper, $this->sshDir, $this->logger, + $this->selfUpdateManager, ); }