diff --git a/system/Commands/Housekeeping/ClearLogs.php b/system/Commands/Housekeeping/ClearLogs.php index 4c6633076a58..008cb630deac 100644 --- a/system/Commands/Housekeeping/ClearLogs.php +++ b/system/Commands/Housekeeping/ClearLogs.php @@ -39,7 +39,7 @@ protected function interact(array &$arguments, array &$options): void return; } - if (CLI::prompt('Are you sure you want to delete the logs?', ['n', 'y']) === 'n') { + if (CLI::prompt(sprintf('Delete all log files in %s?', clean_path(WRITEPATH . 'logs')), ['n', 'y']) === 'n') { return; } @@ -49,24 +49,26 @@ protected function interact(array &$arguments, array &$options): void protected function execute(array $arguments, array $options): int { if ($options['force'] === false) { - CLI::error('Deleting logs aborted.'); + if ($this->isInteractive()) { + CLI::write('Log deletion cancelled.', 'yellow'); - if (! $this->isInteractive()) { - CLI::error('If you want, use the "--force" option to force delete all log files.'); + return EXIT_SUCCESS; } + CLI::error('Log deletion aborted: pass --force to delete log files in non-interactive mode.'); + return EXIT_ERROR; } helper('filesystem'); if (! delete_files(WRITEPATH . 'logs', htdocs: true)) { - CLI::error('Error in deleting the logs files.'); + CLI::error(sprintf('Failed to delete log files in %s.', clean_path(WRITEPATH . 'logs'))); return EXIT_ERROR; } - CLI::write('Logs cleared.', 'green'); + CLI::write(sprintf('Log files in %s cleared.', clean_path(WRITEPATH . 'logs')), 'green'); return EXIT_SUCCESS; } diff --git a/tests/system/Commands/Housekeeping/ClearLogsTest.php b/tests/system/Commands/Housekeeping/ClearLogsTest.php index bf9b43efd111..79f6cd7a42ff 100644 --- a/tests/system/Commands/Housekeeping/ClearLogsTest.php +++ b/tests/system/Commands/Housekeeping/ClearLogsTest.php @@ -81,10 +81,13 @@ public function testClearLogsUsingForce(): void $this->assertFileDoesNotExist(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log"); $this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . 'index.html'); - $this->assertSame("\nLogs cleared.\n", preg_replace('/\e\[[^m]+m/', '', $this->getStreamFilterBuffer())); + $this->assertSame( + sprintf("\nLog files in %s cleared.\n", clean_path(WRITEPATH . 'logs')), + preg_replace('/\e\[[^m]+m/', '', $this->getStreamFilterBuffer()), + ); } - public function testClearLogsAbortsClearWithoutForce(): void + public function testClearLogsCancelsWithoutForce(): void { $this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log"); @@ -96,16 +99,19 @@ public function testClearLogsAbortsClearWithoutForce(): void $this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log"); $this->assertSame( - <<<'EOT' - Are you sure you want to delete the logs? [n, y]: n - Deleting logs aborted. - - EOT, + sprintf( + <<<'EOT' + Delete all log files in %s? [n, y]: n + Log deletion cancelled. + + EOT, + clean_path(WRITEPATH . 'logs'), + ), preg_replace('/\e\[[^m]+m/', '', $io->getOutput()), ); } - public function testClearLogsAbortsClearWithoutForceWithDefaultAnswer(): void + public function testClearLogsCancelsWithoutForceWithDefaultAnswer(): void { $this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log"); @@ -119,17 +125,20 @@ public function testClearLogsAbortsClearWithoutForceWithDefaultAnswer(): void $this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log"); $this->assertSame( - <<getOutput()), ); } - #[DataProvider('provideClearLogsAbortsNonInteractivelyAndHintsAboutForceFlag')] - public function testClearLogsAbortsNonInteractivelyAndHintsAboutForceFlag(string $flag): void + #[DataProvider('provideClearLogsAbortsNonInteractively')] + public function testClearLogsAbortsNonInteractively(string $flag): void { $this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log"); @@ -139,8 +148,7 @@ public function testClearLogsAbortsNonInteractivelyAndHintsAboutForceFlag(string $this->assertSame( <<<'EOT' - Deleting logs aborted. - If you want, use the "--force" option to force delete all log files. + Log deletion aborted: pass --force to delete log files in non-interactive mode. EOT, preg_replace('/\e\[[^m]+m/', '', $this->getStreamFilterBuffer()), @@ -150,7 +158,7 @@ public function testClearLogsAbortsNonInteractivelyAndHintsAboutForceFlag(string /** * @return iterable */ - public static function provideClearLogsAbortsNonInteractivelyAndHintsAboutForceFlag(): iterable + public static function provideClearLogsAbortsNonInteractively(): iterable { yield 'long form' => ['--no-interaction']; @@ -169,11 +177,14 @@ public function testClearLogsWithoutForceButWithConfirmation(): void $this->assertFileDoesNotExist(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log"); $this->assertSame( - <<<'EOT' - Are you sure you want to delete the logs? [n, y]: y - Logs cleared. - - EOT, + sprintf( + <<<'EOT' + Delete all log files in %1$s? [n, y]: y + Log files in %1$s cleared. + + EOT, + clean_path(WRITEPATH . 'logs'), + ), preg_replace('/\e\[[^m]+m/', '', $io->getOutput()), ); } @@ -194,7 +205,7 @@ public function testClearLogsFailsOnChmodFailure(): void $this->assertFileExists($path); $this->assertSame( - "\nError in deleting the logs files.\n", + sprintf("\nFailed to delete log files in %s.\n", clean_path(WRITEPATH . 'logs')), preg_replace('/\e\[[^m]+m/', '', $this->getStreamFilterBuffer()), ); } diff --git a/user_guide_src/source/changelogs/v4.8.0.rst b/user_guide_src/source/changelogs/v4.8.0.rst index 9efb8fd61ea8..c8a074d963fc 100644 --- a/user_guide_src/source/changelogs/v4.8.0.rst +++ b/user_guide_src/source/changelogs/v4.8.0.rst @@ -30,6 +30,7 @@ Behavior Changes - **Commands:** The ``filter:check`` command now requires the HTTP method argument to be uppercase (e.g., ``spark filter:check GET /`` instead of ``spark filter:check get /``). - **Commands:** Several built-in commands have been migrated from ``BaseCommand`` to the modern ``AbstractCommand`` style. Applications that extend a built-in command to override behaviour may need to re-implement against the modern API (``configure()`` + ``execute()`` and the ``#[Command]`` attribute) once the class it extends is migrated, or, preferably, compose instead of extending. Invocations on the command line are unaffected. +- **Commands:** The ``logs:clear`` command now returns ``EXIT_SUCCESS`` (previously ``EXIT_ERROR``) when the user declines the interactive confirmation prompt, since user-initiated cancellation is not a failure. Output messages have also been reworded to distinguish cancellation (interactive ``n``) from abort (non-interactive without ``--force``), and the resolved log directory path is now included in the prompt, success, and failure messages. - **Database:** The Postgre driver's ``$db->error()['code']`` previously always returned ``''``. It now returns the 5-character SQLSTATE string for query and transaction failures (e.g., ``'42P01'``), or ``'08006'`` for connection-level failures. Code that relied on ``$db->error()['code'] === ''`` will need updating. - **Filters:** HTTP method matching for method-based filters is now case-sensitive. The keys in ``Config\Filters::$methods`` must exactly match the request method (e.g., ``GET``, ``POST``). Lowercase method names (e.g., ``post``) will no longer match.