diff --git a/src/Concerns/Fallback.php b/src/Concerns/Fallback.php index 9af0e123..56c7cfcc 100644 --- a/src/Concerns/Fallback.php +++ b/src/Concerns/Fallback.php @@ -3,6 +3,7 @@ namespace Laravel\Prompts\Concerns; use Closure; +use RuntimeException; trait Fallback { @@ -49,7 +50,11 @@ public static function fallbackUsing(Closure $fallback): void */ public function fallback(): mixed { - $fallback = static::$fallbacks[static::class]; + $fallback = static::$fallbacks[static::class] ?? null; + + if ($fallback === null) { + throw new RuntimeException('No fallback implementation registered for ['.static::class.']'); + } return $fallback($this); } diff --git a/src/Prompt.php b/src/Prompt.php index 7770a972..36f5fbbb 100644 --- a/src/Prompt.php +++ b/src/Prompt.php @@ -6,6 +6,7 @@ use Laravel\Prompts\Output\ConsoleOutput; use RuntimeException; use Symfony\Component\Console\Output\OutputInterface; +use Throwable; abstract class Prompt { @@ -80,12 +81,20 @@ public function prompt(): mixed $this->checkEnvironment(); + try { + static::terminal()->setTty('-icanon -isig -echo'); + } catch (Throwable $e) { + static::output()->writeln("{$e->getMessage()}"); + static::fallbackWhen(true); + + return $this->fallback(); + } + register_shutdown_function(function () { $this->restoreCursor(); static::terminal()->restoreTty(); }); - static::terminal()->setTty('-icanon -isig -echo'); $this->hideCursor(); $this->render(); diff --git a/src/Terminal.php b/src/Terminal.php index 2575984c..ef622ff6 100644 --- a/src/Terminal.php +++ b/src/Terminal.php @@ -2,6 +2,7 @@ namespace Laravel\Prompts; +use RuntimeException; use Symfony\Component\Console\Terminal as SymfonyTerminal; class Terminal @@ -36,9 +37,9 @@ public function read(): string */ public function setTty(string $mode): void { - $this->initialTtyMode ??= (shell_exec('stty -g') ?: null); + $this->initialTtyMode ??= $this->exec('stty -g'); - shell_exec("stty $mode"); + $this->exec("stty $mode"); } /** @@ -47,7 +48,7 @@ public function setTty(string $mode): void public function restoreTty(): void { if ($this->initialTtyMode) { - shell_exec("stty {$this->initialTtyMode}"); + $this->exec("stty {$this->initialTtyMode}"); $this->initialTtyMode = null; } @@ -76,4 +77,29 @@ public function exit(): void { exit(1); } + + /** + * Execute the given command and return the output. + */ + protected function exec(string $command): string + { + $process = proc_open($command, [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ], $pipes); + + if (! $process) { + throw new RuntimeException('Failed to create process.'); + } + + $stdout = stream_get_contents($pipes[1]); + $stderr = stream_get_contents($pipes[2]); + $code = proc_close($process); + + if ($code !== 0 || $stdout === false) { + throw new RuntimeException(trim($stderr ?: "Unknown error (code: $code)"), $code); + } + + return $stdout; + } }