-
- Zurück
-
+
+
+ @livewire('moox-prompts.filament.components.run-command-component', [
+ 'command' => $selectedCommand,
+ 'commandInput' => [],
+ ], key('run-command-' . $selectedCommand))
+
+
+
+
+ Zurück zur Command-Auswahl
+
+
@endif
diff --git a/packages/prompts/src/Filament/Components/RunCommandComponent.php b/packages/prompts/src/Filament/Components/RunCommandComponent.php
index 91a3ef580..e69808f39 100644
--- a/packages/prompts/src/Filament/Components/RunCommandComponent.php
+++ b/packages/prompts/src/Filament/Components/RunCommandComponent.php
@@ -10,14 +10,14 @@
use Filament\Forms\Components\TextInput;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
-use Illuminate\Console\OutputStyle;
use Illuminate\Support\Facades\Validator;
use Livewire\Component;
-use Moox\Prompts\Support\PendingPromptsException;
+use Moox\Prompts\Support\PromptFlowRunner;
+use Moox\Prompts\Support\PromptFlowStateStore;
use Moox\Prompts\Support\PromptResponseStore;
+use Moox\Prompts\Support\PromptRuntime;
+use Moox\Prompts\Support\WebPromptRuntime;
use Moox\Prompts\Support\WebCommandRunner;
-use Symfony\Component\Console\Input\ArrayInput;
-use Symfony\Component\Console\Output\BufferedOutput;
use Throwable;
class RunCommandComponent extends Component implements HasForms
@@ -48,6 +48,8 @@ class RunCommandComponent extends Component implements HasForms
public int $executionStep = 0;
+ public ?string $flowId = null;
+
protected PromptResponseStore $responseStore;
public function boot(): void
@@ -71,8 +73,9 @@ public function mount(string $command = '', array $commandInput = []): void
$this->error = null;
$this->commandStarted = false;
$this->executionStep = 0;
+ $this->flowId = null;
- $this->responseStore->resetCounter();
+ $this->responseStore->clear();
if ($command) {
$this->runCommand();
@@ -85,103 +88,72 @@ protected function runCommand(): void
$this->isComplete = false;
try {
- // Stelle sicher, dass publishable Resources registriert sind (auch im Web-Kontext)
- // Dies löst das Problem, dass Spatie Package Tools nur registriert, wenn runningInConsole() true ist
+ // erzwinge Web-Runtime (nicht CLI) im Web-Kontext
+ app()->instance(PromptRuntime::class, new WebPromptRuntime);
WebCommandRunner::ensurePublishableResourcesRegistered();
- $this->responseStore->resetCounter();
+ $runner = app(PromptFlowRunner::class);
+ $stateStore = app(PromptFlowStateStore::class);
- foreach ($this->answers as $promptId => $answer) {
- $this->responseStore->set($promptId, $answer);
+ $state = $this->flowId ? $stateStore->get($this->flowId) : null;
+ if (! $state) {
+ // frischer Flow: ResponseStore und lokale States leeren
+ $this->responseStore->clear();
+ $this->responseStore->resetCounter();
+ $this->answers = [];
+ $this->data = [];
+ $this->currentPrompt = null;
+ $this->isComplete = false;
+ $this->error = null;
+ $this->output = '';
+ $this->currentStepOutput = '';
+ $state = $runner->start($this->command, $this->commandInput);
+ $this->flowId = $state->flowId;
}
+ // Antworten in den ResponseStore spiegeln (ohne den Zähler zu manipulieren)
foreach ($this->answers as $promptId => $answer) {
$this->responseStore->set($promptId, $answer);
}
app()->instance('moox.prompts.response_store', $this->responseStore);
- $commandInstance = app(\Illuminate\Contracts\Console\Kernel::class)
- ->all()[$this->command] ?? null;
+ while (true) {
+ $result = $runner->runNext($state, $this->commandInput, $this->responseStore);
+ $this->appendOutput($result['output'] ?? '');
+ $state = $result['state'];
- if (! $commandInstance) {
- $this->error = "Command nicht gefunden: {$this->command}";
+ if (! empty($result['prompt'])) {
+ $this->currentStepOutput = $this->output ?? '';
+ $this->currentPrompt = $result['prompt'];
+ $this->executionStep++;
+ $this->prefillPromptForm($result['prompt']);
- return;
- }
+ return;
+ }
- $commandInstance->setLaravel(app());
- $output = new BufferedOutput;
+ if (! empty($result['failed'])) {
+ $this->currentStepOutput = $this->output;
+ $this->error = $result['error'] ?? 'Unbekannter Fehler';
+ $this->currentPrompt = null;
- $outputStyle = new OutputStyle(
- new ArrayInput($this->commandInput),
- $output
- );
- $commandInstance->setOutput($outputStyle);
-
- try {
- $commandInstance->run(
- new ArrayInput($this->commandInput),
- $output
- );
-
- $newOutput = $output->fetch();
- // Kumulativer Output: Füge neuen Output zum bestehenden hinzu
- if (! empty($newOutput)) {
- if (! empty($this->output)) {
- $this->output .= "\n".$newOutput;
- } else {
- $this->output = $newOutput;
- }
- }
- // Am Ende: Zeige den vollständigen Output
- $this->currentStepOutput = $this->output;
- $this->isComplete = true;
- $this->currentPrompt = null;
- } catch (PendingPromptsException $e) {
- $newOutput = $output->fetch();
- // Kumulativer Output: Füge neuen Output zum bestehenden hinzu
- if (! empty($newOutput)) {
- if (! empty($this->output)) {
- $this->output .= "\n".$newOutput;
- } else {
- $this->output = $newOutput;
- }
+ return;
}
- // Zeige kumulativen Output während Prompts für Debugging
- $this->currentStepOutput = $this->output ?? '';
-
- $prompt = $e->getPrompt();
- $this->currentPrompt = $prompt;
- $this->executionStep++;
-
- $promptId = $prompt['id'] ?? null;
- if ($promptId && isset($this->answers[$promptId])) {
- $value = $this->answers[$promptId];
- if ($prompt['method'] === 'multiselect') {
- if (! is_array($value)) {
- if ($value === true) {
- $params = $prompt['params'] ?? [];
- $options = $params[1] ?? [];
- $value = array_keys($options);
- } else {
- $value = [];
- }
- }
- }
- $this->form->fill([$promptId => $value]);
+
+ if (! empty($result['completed'])) {
+ $this->currentStepOutput = $this->output;
+ $this->isComplete = true;
+ $this->currentPrompt = null;
+ $this->responseStore->clear();
+ $this->responseStore->resetCounter();
+ $this->answers = [];
+ $this->data = [];
+ $this->flowId = null;
+
+ return;
}
}
} catch (Throwable $e) {
- $newOutput = isset($output) ? $output->fetch() : '';
- // Kumulativer Output auch bei Exceptions
- if (! empty($newOutput)) {
- if (! empty($this->output)) {
- $this->output .= "\n".$newOutput;
- } else {
- $this->output = $newOutput;
- }
- }
$this->output = $this->appendExceptionToOutput($this->output, $e);
$this->currentStepOutput = $this->output;
$this->error = $this->formatThrowableMessage($e);
@@ -189,6 +161,42 @@ protected function runCommand(): void
}
}
+ protected function appendOutput(?string $newOutput): void
+ {
+ if (empty($newOutput)) {
+ return;
+ }
+
+ if (! empty($this->output)) {
+ $this->output .= "\n".$newOutput;
+ } else {
+ $this->output = $newOutput;
+ }
+ }
+
+ protected function prefillPromptForm(array $prompt): void
+ {
+ $promptId = $prompt['id'] ?? null;
+ if (! $promptId || ! isset($this->answers[$promptId])) {
+ return;
+ }
+
+ $value = $this->answers[$promptId];
+ if (($prompt['method'] ?? '') === 'multiselect') {
+ if (! is_array($value)) {
+ if ($value === true) {
+ $params = $prompt['params'] ?? [];
+ $options = $params[1] ?? [];
+ $value = array_keys($options);
+ } else {
+ $value = [];
+ }
+ }
+ }
+
+ $this->form->fill([$promptId => $value]);
+ }
+
protected function formatThrowableMessage(Throwable $e): string
{
return sprintf(
@@ -285,18 +293,15 @@ public function submitPrompt(): void
}
}
} catch (\Illuminate\Validation\ValidationException $e) {
- // Capture Filament validation errors
$errors = $e->errors();
$this->validationErrors = [];
- // Get errors for current prompt field
if (isset($errors[$promptId])) {
$this->validationErrors = is_array($errors[$promptId])
? $errors[$promptId]
: [$errors[$promptId]];
}
- // If no specific field errors, get all errors
if (empty($this->validationErrors)) {
foreach ($errors as $fieldErrors) {
if (is_array($fieldErrors)) {
@@ -311,7 +316,6 @@ public function submitPrompt(): void
}
}
- // Validate empty fields before returning
if ($this->currentPrompt['method'] === 'confirm') {
if ($answer === null) {
$this->validatePromptAnswer($promptId, null, $this->currentPrompt);
@@ -319,7 +323,6 @@ public function submitPrompt(): void
return;
}
} elseif ($this->currentPrompt['method'] === 'select') {
- // Für Select: Prüfe ob leer oder null
if ($answer === null || $answer === '' || $answer === '0') {
$this->validatePromptAnswer($promptId, $answer, $this->currentPrompt);
@@ -361,6 +364,7 @@ public function submitPrompt(): void
}
$this->answers[$promptId] = $answer;
+ $this->responseStore->set($promptId, $answer);
$this->currentPrompt = null;
$this->runCommand();
} catch (\Exception $e) {
@@ -376,7 +380,6 @@ protected function validatePromptAnswer(string $promptId, mixed $answer, array $
$rules = [];
$messages = [];
- // Map required flag per method (parameter positions differ)
$requiredFlag = match ($method) {
'text', 'textarea', 'password' => $params[3] ?? false,
'multiselect' => $params[3] ?? false,
@@ -384,12 +387,10 @@ protected function validatePromptAnswer(string $promptId, mixed $answer, array $
default => false,
};
- // Multiselect: erzwinge Auswahl, wenn Optionen vorhanden
if ($method === 'multiselect' && ! empty($params[1] ?? [])) {
$requiredFlag = true;
}
- // Map validate parameter per method (avoid treating confirm/labels as rules)
$validate = match ($method) {
'text', 'textarea', 'password' => $params[4] ?? null,
'select' => $params[5] ?? null,
@@ -397,7 +398,6 @@ protected function validatePromptAnswer(string $promptId, mixed $answer, array $
default => null,
};
- // Normalize rule strings (split pipe-delimited)
$pushRules = function (array &$into, string|array|null $value): void {
if ($value === null || $value === false || $value === '') {
return;
@@ -454,13 +454,11 @@ protected function validatePromptAnswer(string $promptId, mixed $answer, array $
}
if (! empty($rules)) {
- // Freundlichere Meldungen speziell für Multiselect
if ($method === 'multiselect') {
$messages["{$promptId}.required"] = 'Bitte mindestens eine Option wählen.';
$messages["{$promptId}.min"] = 'Bitte mindestens eine Option wählen.';
}
- // Freundlichere Meldungen speziell für Select
if ($method === 'select') {
$messages["{$promptId}.required"] = 'Bitte wählen Sie eine Option aus.';
$messages["{$promptId}.in"] = 'Bitte wählen Sie eine gültige Option aus.';
@@ -546,12 +544,11 @@ protected function createMultiselectFields(string $promptId, array $params): arr
protected function createFieldFromPrompt(string $promptId, string $method, array $params): ?\Filament\Forms\Components\Field
{
$label = $params[0] ?? '';
- // Determine required flag per prompt type (indexes differ)
$required = match ($method) {
'text', 'textarea', 'password' => ($params[3] ?? false) !== false,
'multiselect' => ($params[3] ?? false) !== false,
'confirm' => ($params[2] ?? false) !== false,
- 'select' => ($params[2] ?? null) === null, // Required wenn kein Default gesetzt
+ 'select' => ($params[2] ?? null) === null,
default => false,
};
$defaultValue = $this->answers[$promptId] ?? null;
@@ -559,7 +556,6 @@ protected function createFieldFromPrompt(string $promptId, string $method, array
$defaultSelect = $defaultValue ?? ($params[2] ?? null);
$confirmDefault = $defaultValue ?? ($params[1] ?? false);
- // Map validate parameter per method
$validate = match ($method) {
'text', 'textarea', 'password' => $params[4] ?? null,
'select' => $params[5] ?? null,
@@ -567,7 +563,6 @@ protected function createFieldFromPrompt(string $promptId, string $method, array
default => null,
};
- // Build validation rules for Filament fields (normalize pipe-delimited)
$rules = [];
$pushRules = function (array &$into, string|array|null $value): void {
if ($value === null || $value === false || $value === '') {
@@ -586,7 +581,6 @@ protected function createFieldFromPrompt(string $promptId, string $method, array
$rules[] = 'required';
}
- // Add method-specific rules
if ($method === 'select' && $required && ! empty($options)) {
$rules[] = 'in:'.implode(',', array_keys($options));
}
@@ -597,8 +591,6 @@ protected function createFieldFromPrompt(string $promptId, string $method, array
$pushRules($rules, $validate);
- // Kein automatisches Default für Select - Validation wird verwendet
-
return match ($method) {
'text' => TextInput::make($promptId)
->label($label)
@@ -656,3 +648,4 @@ public function render()
return view('moox-prompts::filament.components.run-command');
}
}
+
\ No newline at end of file
diff --git a/packages/prompts/src/PromptsServiceProvider.php b/packages/prompts/src/PromptsServiceProvider.php
index 33b45e292..fdc4f2801 100644
--- a/packages/prompts/src/PromptsServiceProvider.php
+++ b/packages/prompts/src/PromptsServiceProvider.php
@@ -4,6 +4,8 @@
use Moox\Core\MooxServiceProvider;
use Moox\Prompts\Support\CliPromptRuntime;
+use Moox\Prompts\Support\PromptFlowRunner;
+use Moox\Prompts\Support\PromptFlowStateStore;
use Moox\Prompts\Support\PromptResponseStore;
use Moox\Prompts\Support\PromptRuntime;
use Moox\Prompts\Support\WebPromptRuntime;
@@ -27,6 +29,28 @@ public function register()
return new PromptResponseStore;
});
+ $this->app->singleton(PromptFlowStateStore::class, function ($app) {
+ $store = cache()->store();
+
+ // Avoid volatile in-memory array cache which loses flow state between requests.
+ if ($store->getStore() instanceof \Illuminate\Cache\ArrayStore) {
+ $store = cache()->store('file');
+ }
+
+ return new PromptFlowStateStore(
+ $store,
+ 'moox_prompts_flow:',
+ 3600
+ );
+ });
+
+ $this->app->singleton(PromptFlowRunner::class, function ($app) {
+ return new PromptFlowRunner(
+ $app->make(\Illuminate\Contracts\Console\Kernel::class),
+ $app->make(PromptFlowStateStore::class)
+ );
+ });
+
$this->app->singleton(PromptRuntime::class, function ($app) {
if (php_sapi_name() === 'cli') {
return new CliPromptRuntime;
@@ -51,4 +75,5 @@ public function boot(): void
);
}
}
+
}
diff --git a/packages/prompts/src/Support/CliPromptRuntime.php b/packages/prompts/src/Support/CliPromptRuntime.php
index b2756ea2d..409b30742 100644
--- a/packages/prompts/src/Support/CliPromptRuntime.php
+++ b/packages/prompts/src/Support/CliPromptRuntime.php
@@ -9,12 +9,6 @@
class CliPromptRuntime implements PromptRuntime
{
- /*
- |--------------------------------------------------------------------------
- | Input Prompts
- |--------------------------------------------------------------------------
- */
-
public function text(
string $label,
string $placeholder = '',
@@ -202,23 +196,11 @@ public function multisearch(
);
}
- /*
- |--------------------------------------------------------------------------
- | Auxiliary Prompts
- |--------------------------------------------------------------------------
- */
-
public function pause(string $message = 'Press ENTER to continue'): void
{
Prompts\pause($message);
}
- /*
- |--------------------------------------------------------------------------
- | Informational
- |--------------------------------------------------------------------------
- */
-
public function note(string $message): void
{
Prompts\note($message);
@@ -254,23 +236,11 @@ public function outro(string $message): void
Prompts\outro($message);
}
- /*
- |--------------------------------------------------------------------------
- | Table Output
- |--------------------------------------------------------------------------
- */
-
public function table(array $headers, array $rows): void
{
Prompts\table($headers, $rows);
}
- /*
- |--------------------------------------------------------------------------
- | Spinner
- |--------------------------------------------------------------------------
- */
-
public function spin(
Closure $callback,
string $message = '',
@@ -278,12 +248,6 @@ public function spin(
return Prompts\spin($callback, $message);
}
- /*
- |--------------------------------------------------------------------------
- | Progress
- |--------------------------------------------------------------------------
- */
-
public function progress(
string $label,
iterable|int $steps,
@@ -298,23 +262,11 @@ public function progress(
);
}
- /*
- |--------------------------------------------------------------------------
- | Clear Terminal
- |--------------------------------------------------------------------------
- */
-
public function clear(): void
{
Prompts\clear();
}
- /*
- |--------------------------------------------------------------------------
- | Form
- |--------------------------------------------------------------------------
- */
-
public function form(): FormBuilder
{
return Prompts\form();
diff --git a/packages/prompts/src/Support/FlowCommand.php b/packages/prompts/src/Support/FlowCommand.php
new file mode 100644
index 000000000..6f7b4d4f7
--- /dev/null
+++ b/packages/prompts/src/Support/FlowCommand.php
@@ -0,0 +1,39 @@
+promptFlowSteps() as $step) {
+ if (method_exists($this, $step)) {
+ $this->{$step}();
+ }
+ }
+
+ return self::SUCCESS;
+ }
+}
+
+
diff --git a/packages/prompts/src/Support/PromptFlowRunner.php b/packages/prompts/src/Support/PromptFlowRunner.php
new file mode 100644
index 000000000..29b1af2e4
--- /dev/null
+++ b/packages/prompts/src/Support/PromptFlowRunner.php
@@ -0,0 +1,209 @@
+resolveCommand($commandName);
+ $steps = ($command instanceof PromptFlowCommand)
+ ? $command->promptFlowSteps()
+ : ['handle'];
+
+ if (empty($steps)) {
+ $steps = ['handle'];
+ }
+
+ return $this->stateStore->create($commandName, array_values($steps));
+ }
+
+ public function get(string $flowId): ?PromptFlowState
+ {
+ return $this->stateStore->get($flowId);
+ }
+
+ public function runNext(
+ PromptFlowState $state,
+ array $commandInput,
+ PromptResponseStore $responseStore,
+ ): array {
+ $step = $state->nextPendingStep();
+
+ if ($step === null) {
+ return [
+ 'output' => '',
+ 'prompt' => null,
+ 'completed' => true,
+ 'failed' => false,
+ 'error' => null,
+ 'state' => $state,
+ ];
+ }
+
+ $command = $this->resolveCommand($state->commandName);
+ $command->setLaravel(app());
+
+ $input = new ArrayInput($commandInput);
+ $output = new BufferedOutput;
+ $outputStyle = new OutputStyle($input, $output);
+ $command->setOutput($outputStyle);
+ $this->setCommandInput($command, $input);
+
+ try {
+ app()->instance('moox.prompts.response_store', $responseStore);
+ // aktuell ausgeführten Step für die Web-Runtime verfügbar machen
+ app()->instance('moox.prompts.current_step', $step);
+
+ // restore persisted command properties (e.g., choice) across steps
+ $this->restoreCommandContext($command, $state);
+
+ $this->invokeStep($command, $step);
+
+ // persist selected command properties back into state
+ $this->captureCommandContext($command, $state);
+
+ $stepOutput = $output->fetch();
+ $state->markStepFinished($step, $stepOutput);
+ $this->stateStore->put($state);
+
+ return [
+ 'output' => $stepOutput,
+ 'prompt' => null,
+ 'completed' => $state->completed,
+ 'failed' => false,
+ 'error' => null,
+ 'state' => $state,
+ ];
+ } catch (PendingPromptsException $e) {
+ $stepOutput = $output->fetch();
+ $this->captureCommandContext($command, $state);
+ $this->stateStore->put($state);
+
+ return [
+ 'output' => $stepOutput,
+ 'prompt' => $e->getPrompt(),
+ 'completed' => false,
+ 'failed' => false,
+ 'error' => null,
+ 'state' => $state,
+ ];
+ } catch (Throwable $e) {
+ $stepOutput = $output->fetch();
+ $state->markFailed($step, $e->getMessage());
+ $this->captureCommandContext($command, $state);
+ $this->stateStore->put($state);
+
+ return [
+ 'output' => $this->appendExceptionToOutput($stepOutput, $e),
+ 'prompt' => null,
+ 'completed' => false,
+ 'failed' => true,
+ 'error' => $e->getMessage(),
+ 'state' => $state,
+ ];
+ }
+ }
+
+ protected function appendExceptionToOutput(string $output, Throwable $e): string
+ {
+ $trace = $e->getTraceAsString();
+
+ return trim($output."\n\n".$this->formatThrowableMessage($e)."\n".$trace);
+ }
+
+ protected function formatThrowableMessage(Throwable $e): string
+ {
+ return sprintf(
+ '%s: %s in %s:%d',
+ $e::class,
+ $e->getMessage(),
+ $e->getFile(),
+ $e->getLine()
+ );
+ }
+
+ protected function resolveCommand(string $commandName)
+ {
+ $commandInstance = $this->artisan->all()[$commandName] ?? null;
+
+ if (! $commandInstance) {
+ throw new \RuntimeException("Command nicht gefunden: {$commandName}");
+ }
+
+ return $commandInstance;
+ }
+
+ protected function invokeStep($command, string $method): void
+ {
+ if (! method_exists($command, $method)) {
+ throw new \RuntimeException("Step {$method} nicht gefunden auf Command ".get_class($command));
+ }
+
+ $command->{$method}();
+ }
+
+ protected function setCommandInput($command, ArrayInput $input): void
+ {
+ $ref = new \ReflectionClass($command);
+
+ if ($ref->hasProperty('input')) {
+ $prop = $ref->getProperty('input');
+ $prop->setAccessible(true);
+ $prop->setValue($command, $input);
+ }
+ }
+
+ protected function captureCommandContext($command, PromptFlowState $state): void
+ {
+ $ref = new \ReflectionObject($command);
+
+ // Wir persistieren alle nicht-statischen Properties, die auf der konkreten Command-Klasse
+ // deklariert sind und skalare/Array-Werte enthalten (z.B. choice, features, projectName, ...).
+ foreach ($ref->getProperties() as $prop) {
+ if ($prop->isStatic()) {
+ continue;
+ }
+
+ if ($prop->getDeclaringClass()->getName() !== $ref->getName()) {
+ // Nur Properties der konkreten Command-Klasse, nicht von der Basisklasse
+ continue;
+ }
+
+ $prop->setAccessible(true);
+ $value = $prop->getValue($command);
+
+ if (is_scalar($value) || $value === null || is_array($value)) {
+ $state->context[$prop->getName()] = $value;
+ }
+ }
+ }
+
+ protected function restoreCommandContext($command, PromptFlowState $state): void
+ {
+ if (empty($state->context)) {
+ return;
+ }
+
+ $ref = new \ReflectionObject($command);
+ foreach ($state->context as $propName => $value) {
+ if ($ref->hasProperty($propName)) {
+ $prop = $ref->getProperty($propName);
+ $prop->setAccessible(true);
+ $prop->setValue($command, $value);
+ }
+ }
+ }
+}
+
diff --git a/packages/prompts/src/Support/PromptFlowState.php b/packages/prompts/src/Support/PromptFlowState.php
new file mode 100644
index 000000000..07f34d43f
--- /dev/null
+++ b/packages/prompts/src/Support/PromptFlowState.php
@@ -0,0 +1,43 @@
+completed) {
+ return null;
+ }
+
+ return $this->steps[$this->currentIndex] ?? null;
+ }
+
+ public function markStepFinished(string $step, string $output = ''): void
+ {
+ $this->stepOutputs[$step] = $output;
+ $this->currentIndex++;
+ if ($this->currentIndex >= count($this->steps)) {
+ $this->completed = true;
+ }
+ }
+
+ public function markFailed(string $step, string $message): void
+ {
+ $this->failedAt = $step;
+ $this->errorMessage = $message;
+ }
+}
+
diff --git a/packages/prompts/src/Support/PromptFlowStateStore.php b/packages/prompts/src/Support/PromptFlowStateStore.php
new file mode 100644
index 000000000..b642f5890
--- /dev/null
+++ b/packages/prompts/src/Support/PromptFlowStateStore.php
@@ -0,0 +1,45 @@
+put($state);
+
+ return $state;
+ }
+
+ public function get(string $flowId): ?PromptFlowState
+ {
+ return $this->cache->get($this->key($flowId));
+ }
+
+ public function put(PromptFlowState $state): void
+ {
+ $this->cache->put($this->key($state->flowId), $state, $this->ttlSeconds);
+ }
+
+ public function reset(string $flowId): void
+ {
+ $this->cache->forget($this->key($flowId));
+ }
+
+ protected function key(string $flowId): string
+ {
+ return $this->prefix.$flowId;
+ }
+}
+
diff --git a/packages/prompts/src/Support/PromptRuntime.php b/packages/prompts/src/Support/PromptRuntime.php
index f2582899c..0f024d5c0 100644
--- a/packages/prompts/src/Support/PromptRuntime.php
+++ b/packages/prompts/src/Support/PromptRuntime.php
@@ -101,12 +101,6 @@ public function multisearch(
public function pause(string $message = 'Press ENTER to continue'): void;
- /*
- |--------------------------------------------------------------------------
- | Informational Messages
- |--------------------------------------------------------------------------
- */
-
public function note(string $message): void;
public function info(string $message): void;
@@ -121,31 +115,13 @@ public function intro(string $message): void;
public function outro(string $message): void;
- /*
- |--------------------------------------------------------------------------
- | Table Output
- |--------------------------------------------------------------------------
- */
-
public function table(array $headers, array $rows): void;
- /*
- |--------------------------------------------------------------------------
- | Spinner
- |--------------------------------------------------------------------------
- */
-
public function spin(
Closure $callback,
string $message = '',
): mixed;
- /*
- |--------------------------------------------------------------------------
- | Progress
- |--------------------------------------------------------------------------
- */
-
public function progress(
string $label,
iterable|int $steps,
@@ -153,19 +129,7 @@ public function progress(
string $hint = '',
): Progress|array;
- /*
- |--------------------------------------------------------------------------
- | Clear Terminal
- |--------------------------------------------------------------------------
- */
-
public function clear(): void;
- /*
- |--------------------------------------------------------------------------
- | Form
- |--------------------------------------------------------------------------
- */
-
public function form(): FormBuilder;
}
diff --git a/packages/prompts/src/Support/WebPromptRuntime.php b/packages/prompts/src/Support/WebPromptRuntime.php
index b4068f641..460f70e82 100644
--- a/packages/prompts/src/Support/WebPromptRuntime.php
+++ b/packages/prompts/src/Support/WebPromptRuntime.php
@@ -17,6 +17,14 @@ public function __construct()
protected function generatePromptId(string $method): string
{
+ // Wenn der aktuelle Step vom FlowRunner gesetzt wurde, verwenden wir ihn
+ // als stabile Prompt-ID, damit jeder Step genau einen Prompt hat und
+ // Antworten nicht zwischen Steps vermischt werden.
+ if (app()->bound('moox.prompts.current_step')) {
+ return app('moox.prompts.current_step');
+ }
+
+ // Fallback für generische Nutzung (z.B. CLI oder ohne Flow-Kontext)
return $this->responseStore->getNextPromptId($method);
}
@@ -42,6 +50,13 @@ protected function checkOrThrow(string $promptId, array $promptData): mixed
return $value;
}
+ // Für alle anderen Prompts erwarten wir skalare Werte.
+ // Falls dennoch ein Array im Store liegt (z.B. durch Formular-State),
+ // wandeln wir es in einen String um, um Typfehler zu vermeiden.
+ if (is_array($value)) {
+ return implode(', ', array_map('strval', $value));
+ }
+
return $value;
}
diff --git a/packages/prompts/src/functions.php b/packages/prompts/src/functions.php
index e36e7ea78..43170896c 100644
--- a/packages/prompts/src/functions.php
+++ b/packages/prompts/src/functions.php
@@ -7,12 +7,6 @@
use Laravel\Prompts\Progress;
use Moox\Prompts\Support\PromptRuntime;
-/*
-|--------------------------------------------------------------------------
-| Input Prompts
-|--------------------------------------------------------------------------
-*/
-
function text(
string $label,
string $placeholder = '',
@@ -146,23 +140,11 @@ function multisearch(
);
}
-/*
-|--------------------------------------------------------------------------
-| Auxiliary Prompts
-|--------------------------------------------------------------------------
-*/
-
function pause(string $message = 'Press ENTER to continue'): void
{
app(PromptRuntime::class)->pause($message);
}
-/*
-|--------------------------------------------------------------------------
-| Informational
-|--------------------------------------------------------------------------
-*/
-
function note(string $message): void
{
app(PromptRuntime::class)->note($message);
@@ -198,34 +180,16 @@ function outro(string $message): void
app(PromptRuntime::class)->outro($message);
}
-/*
-|--------------------------------------------------------------------------
-| Table
-|--------------------------------------------------------------------------
-*/
-
function table(array $headers, array $rows): void
{
app(PromptRuntime::class)->table($headers, $rows);
}
-/*
-|--------------------------------------------------------------------------
-| Spinner
-|--------------------------------------------------------------------------
-*/
-
function spin(Closure $callback, string $message = ''): mixed
{
return app(PromptRuntime::class)->spin($callback, $message);
}
-/*
-|--------------------------------------------------------------------------
-| Progress
-|--------------------------------------------------------------------------
-*/
-
function progress(
string $label,
iterable|int $steps,
@@ -237,23 +201,11 @@ function progress(
);
}
-/*
-|--------------------------------------------------------------------------
-| Clear
-|--------------------------------------------------------------------------
-*/
-
function clear(): void
{
app(PromptRuntime::class)->clear();
}
-/*
-|--------------------------------------------------------------------------
-| Form
-|--------------------------------------------------------------------------
-*/
-
function form(): FormBuilder
{
return app(PromptRuntime::class)->form();
From 4b8fa6252c50ddf2a9b68c4de402379a4eb4ab1f Mon Sep 17 00:00:00 2001
From: AzGasim <104441723+AzGasim@users.noreply.github.com>
Date: Mon, 15 Dec 2025 16:02:22 +0000
Subject: [PATCH 06/16] Fix styling
---
.../prompts/src/Filament/Components/RunCommandComponent.php | 3 +--
packages/prompts/src/PromptsServiceProvider.php | 1 -
packages/prompts/src/Support/FlowCommand.php | 2 --
packages/prompts/src/Support/PromptFlowRunner.php | 3 +--
packages/prompts/src/Support/PromptFlowState.php | 1 -
packages/prompts/src/Support/PromptFlowStateStore.php | 1 -
6 files changed, 2 insertions(+), 9 deletions(-)
diff --git a/packages/prompts/src/Filament/Components/RunCommandComponent.php b/packages/prompts/src/Filament/Components/RunCommandComponent.php
index e69808f39..d090bcec2 100644
--- a/packages/prompts/src/Filament/Components/RunCommandComponent.php
+++ b/packages/prompts/src/Filament/Components/RunCommandComponent.php
@@ -16,8 +16,8 @@
use Moox\Prompts\Support\PromptFlowStateStore;
use Moox\Prompts\Support\PromptResponseStore;
use Moox\Prompts\Support\PromptRuntime;
-use Moox\Prompts\Support\WebPromptRuntime;
use Moox\Prompts\Support\WebCommandRunner;
+use Moox\Prompts\Support\WebPromptRuntime;
use Throwable;
class RunCommandComponent extends Component implements HasForms
@@ -648,4 +648,3 @@ public function render()
return view('moox-prompts::filament.components.run-command');
}
}
-
\ No newline at end of file
diff --git a/packages/prompts/src/PromptsServiceProvider.php b/packages/prompts/src/PromptsServiceProvider.php
index fdc4f2801..ec09ff746 100644
--- a/packages/prompts/src/PromptsServiceProvider.php
+++ b/packages/prompts/src/PromptsServiceProvider.php
@@ -75,5 +75,4 @@ public function boot(): void
);
}
}
-
}
diff --git a/packages/prompts/src/Support/FlowCommand.php b/packages/prompts/src/Support/FlowCommand.php
index 6f7b4d4f7..8ce10fc34 100644
--- a/packages/prompts/src/Support/FlowCommand.php
+++ b/packages/prompts/src/Support/FlowCommand.php
@@ -35,5 +35,3 @@ public function handle(): int
return self::SUCCESS;
}
}
-
-
diff --git a/packages/prompts/src/Support/PromptFlowRunner.php b/packages/prompts/src/Support/PromptFlowRunner.php
index 29b1af2e4..514e50380 100644
--- a/packages/prompts/src/Support/PromptFlowRunner.php
+++ b/packages/prompts/src/Support/PromptFlowRunner.php
@@ -2,8 +2,8 @@
namespace Moox\Prompts\Support;
-use Illuminate\Contracts\Console\Kernel;
use Illuminate\Console\OutputStyle;
+use Illuminate\Contracts\Console\Kernel;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Throwable;
@@ -206,4 +206,3 @@ protected function restoreCommandContext($command, PromptFlowState $state): void
}
}
}
-
diff --git a/packages/prompts/src/Support/PromptFlowState.php b/packages/prompts/src/Support/PromptFlowState.php
index 07f34d43f..873e1246e 100644
--- a/packages/prompts/src/Support/PromptFlowState.php
+++ b/packages/prompts/src/Support/PromptFlowState.php
@@ -40,4 +40,3 @@ public function markFailed(string $step, string $message): void
$this->errorMessage = $message;
}
}
-
diff --git a/packages/prompts/src/Support/PromptFlowStateStore.php b/packages/prompts/src/Support/PromptFlowStateStore.php
index b642f5890..54a7b89e4 100644
--- a/packages/prompts/src/Support/PromptFlowStateStore.php
+++ b/packages/prompts/src/Support/PromptFlowStateStore.php
@@ -42,4 +42,3 @@ protected function key(string $flowId): string
return $this->prefix.$flowId;
}
}
-
From 705c064e3dfd56d6d2968460e418ce817ce6759b Mon Sep 17 00:00:00 2001
From: Aziz Gasim <104441723+AzGasim@users.noreply.github.com>
Date: Tue, 16 Dec 2025 15:34:34 +0100
Subject: [PATCH 07/16] refactor: use PromptParamsHelper for robust parameter
access
Replace direct array index access ($params[0], $params[1]) with named
parameter access via PromptParamsHelper. This centralizes parameter
mapping and makes the code more maintainable and robust against Laravel
Prompts updates.
---
.../Components/RunCommandComponent.php | 179 ++++++++++++------
.../src/Support/PromptParamsHelper.php | 98 ++++++++++
2 files changed, 217 insertions(+), 60 deletions(-)
create mode 100644 packages/prompts/src/Support/PromptParamsHelper.php
diff --git a/packages/prompts/src/Filament/Components/RunCommandComponent.php b/packages/prompts/src/Filament/Components/RunCommandComponent.php
index d090bcec2..9426adaea 100644
--- a/packages/prompts/src/Filament/Components/RunCommandComponent.php
+++ b/packages/prompts/src/Filament/Components/RunCommandComponent.php
@@ -14,6 +14,7 @@
use Livewire\Component;
use Moox\Prompts\Support\PromptFlowRunner;
use Moox\Prompts\Support\PromptFlowStateStore;
+use Moox\Prompts\Support\PromptParamsHelper;
use Moox\Prompts\Support\PromptResponseStore;
use Moox\Prompts\Support\PromptRuntime;
use Moox\Prompts\Support\WebCommandRunner;
@@ -177,24 +178,64 @@ protected function appendOutput(?string $newOutput): void
protected function prefillPromptForm(array $prompt): void
{
$promptId = $prompt['id'] ?? null;
- if (! $promptId || ! isset($this->answers[$promptId])) {
+ if (! $promptId) {
return;
}
- $value = $this->answers[$promptId];
- if (($prompt['method'] ?? '') === 'multiselect') {
- if (! is_array($value)) {
- if ($value === true) {
- $params = $prompt['params'] ?? [];
- $options = $params[1] ?? [];
- $value = array_keys($options);
- } else {
- $value = [];
+ $method = $prompt['method'] ?? '';
+ $params = $prompt['params'] ?? [];
+ $p = PromptParamsHelper::extract($method, $params);
+
+ // Wenn bereits eine Antwort vorhanden ist, diese verwenden
+ if (isset($this->answers[$promptId])) {
+ $value = $this->answers[$promptId];
+ if ($method === 'multiselect') {
+ if (! is_array($value)) {
+ if ($value === true) {
+ $options = $p['options'] ?? [];
+ $value = array_keys($options);
+ } else {
+ $value = [];
+ }
}
}
+ $this->form->fill([$promptId => $value]);
+ return;
+ }
+
+ // Ansonsten Default-Wert aus den Prompt-Params verwenden
+ if ($method === 'confirm') {
+ $default = $p['default'] ?? false; // default Parameter (bool)
+ $value = $default ? 'yes' : 'no';
+ $this->form->fill([$promptId => $value]);
+ return;
+ }
+
+ if ($method === 'multiselect') {
+ $defaultValue = $p['default'] ?? []; // default Parameter (array)
+ // Für multiselect müssen wir die einzelnen Checkboxen füllen
+ $options = $p['options'] ?? [];
+ $fillData = [];
+ foreach (array_keys($options) as $key) {
+ $checkboxId = $promptId.'_'.$key;
+ $fillData[$checkboxId] = is_array($defaultValue) && in_array($key, $defaultValue);
+ }
+ if (! empty($fillData)) {
+ $this->form->fill($fillData);
+ }
+ return;
}
- $this->form->fill([$promptId => $value]);
+ $defaultValue = null;
+ if ($method === 'select') {
+ $defaultValue = $p['default'] ?? null; // default Parameter
+ } elseif (in_array($method, ['text', 'textarea', 'password'])) {
+ $defaultValue = $p['default'] ?? ''; // default Parameter
+ }
+
+ if ($defaultValue !== null) {
+ $this->form->fill([$promptId => $defaultValue]);
+ }
}
protected function formatThrowableMessage(Throwable $e): string
@@ -276,7 +317,7 @@ public function submitPrompt(): void
$answer = false;
}
- if (($answer === null || $answer === '' || ($this->currentPrompt['method'] === 'multiselect' && ! is_array($answer))) && $this->currentPrompt['method'] !== 'confirm') {
+ if (($answer === null || $answer === '' || ($this->currentPrompt['method'] === 'multiselect' && ! is_array($answer))) && $this->currentPrompt['method'] !== 'confirm') {
try {
$data = $this->form->getState();
$answer = $data[$promptId] ?? null;
@@ -353,7 +394,11 @@ public function submitPrompt(): void
}
if ($this->currentPrompt['method'] === 'confirm') {
- if (! is_bool($answer)) {
+ if ($answer === 'yes') {
+ $answer = true;
+ } elseif ($answer === 'no') {
+ $answer = false;
+ } elseif (! is_bool($answer)) {
$answer = (bool) $answer;
}
}
@@ -377,24 +422,26 @@ protected function validatePromptAnswer(string $promptId, mixed $answer, array $
$method = $prompt['method'] ?? '';
$params = $prompt['params'] ?? [];
+ $p = PromptParamsHelper::extract($method, $params);
+
$rules = [];
$messages = [];
$requiredFlag = match ($method) {
- 'text', 'textarea', 'password' => $params[3] ?? false,
- 'multiselect' => $params[3] ?? false,
- 'confirm' => $params[2] ?? false,
+ 'text', 'textarea', 'password' => $p['required'] ?? false,
+ 'multiselect' => $p['required'] ?? false,
+ 'confirm' => $p['required'] ?? false,
default => false,
};
- if ($method === 'multiselect' && ! empty($params[1] ?? [])) {
+ if ($method === 'multiselect' && ! empty($p['options'] ?? [])) {
$requiredFlag = true;
}
$validate = match ($method) {
- 'text', 'textarea', 'password' => $params[4] ?? null,
- 'select' => $params[5] ?? null,
- 'multiselect' => $params[6] ?? null,
+ 'text', 'textarea', 'password' => $p['validate'] ?? null,
+ 'select' => $p['validate'] ?? null,
+ 'multiselect' => $p['validate'] ?? null,
default => null,
};
@@ -420,12 +467,12 @@ protected function validatePromptAnswer(string $promptId, mixed $answer, array $
}
}
- if ($method === 'confirm') {
- $rules[] = 'boolean';
+ if ($method === 'confirm' && $requiredFlag !== false) {
+ $rules[] = 'required';
}
if ($method === 'select' && $requiredFlag !== false) {
- $rules[] = 'in:'.implode(',', array_keys($params[1] ?? []));
+ $rules[] = 'in:'.implode(',', array_keys($p['options'] ?? []));
}
$pushRules($rules, $validate);
@@ -449,19 +496,25 @@ protected function validatePromptAnswer(string $promptId, mixed $answer, array $
$callableErrors[] = $result;
}
if ($result === false) {
- $callableErrors[] = 'Ungültiger Wert.';
+ $callableErrors[] = __('moox-prompts::prompts.validation.callable_invalid');
}
}
if (! empty($rules)) {
+ $label = $p['label'] ?? $promptId;
+
+ if (in_array($method, ['text', 'textarea', 'password'])) {
+ $messages["{$promptId}.required"] = __('moox-prompts::prompts.validation.text_required', ['label' => $label]);
+ }
+
if ($method === 'multiselect') {
- $messages["{$promptId}.required"] = 'Bitte mindestens eine Option wählen.';
- $messages["{$promptId}.min"] = 'Bitte mindestens eine Option wählen.';
+ $messages["{$promptId}.required"] = __('moox-prompts::prompts.validation.multiselect_required');
+ $messages["{$promptId}.min"] = __('moox-prompts::prompts.validation.multiselect_min');
}
if ($method === 'select') {
- $messages["{$promptId}.required"] = 'Bitte wählen Sie eine Option aus.';
- $messages["{$promptId}.in"] = 'Bitte wählen Sie eine gültige Option aus.';
+ $messages["{$promptId}.required"] = __('moox-prompts::prompts.validation.select_required');
+ $messages["{$promptId}.in"] = __('moox-prompts::prompts.validation.select_in');
}
$validator = Validator::make(
@@ -517,10 +570,13 @@ protected function getFormSchema(): array
protected function createMultiselectFields(string $promptId, array $params): array
{
- $label = $params[0] ?? '';
- $required = ($params[3] ?? false) !== false;
- $defaultValue = $this->answers[$promptId] ?? null;
- $options = $params[1] ?? [];
+ $p = PromptParamsHelper::extract('multiselect', $params);
+
+ $label = $p['label'] ?? '';
+ $required = ($p['required'] ?? false) !== false;
+ // Default-Wert: erst aus answers, dann aus default-Parameter
+ $defaultValue = $this->answers[$promptId] ?? ($p['default'] ?? []);
+ $options = $p['options'] ?? [];
$fields = [];
@@ -543,23 +599,30 @@ protected function createMultiselectFields(string $promptId, array $params): arr
protected function createFieldFromPrompt(string $promptId, string $method, array $params): ?\Filament\Forms\Components\Field
{
- $label = $params[0] ?? '';
+ $p = PromptParamsHelper::extract($method, $params);
+
+ $label = $p['label'] ?? '';
$required = match ($method) {
- 'text', 'textarea', 'password' => ($params[3] ?? false) !== false,
- 'multiselect' => ($params[3] ?? false) !== false,
- 'confirm' => ($params[2] ?? false) !== false,
- 'select' => ($params[2] ?? null) === null,
+ 'text', 'textarea', 'password' => ($p['required'] ?? false) !== false,
+ 'multiselect' => ($p['required'] ?? false) !== false,
+ 'confirm' => ($p['required'] ?? false) !== false,
+ 'select' => ($p['default'] ?? null) === null,
default => false,
};
$defaultValue = $this->answers[$promptId] ?? null;
- $options = $params[1] ?? [];
- $defaultSelect = $defaultValue ?? ($params[2] ?? null);
- $confirmDefault = $defaultValue ?? ($params[1] ?? false);
+ $options = $p['options'] ?? [];
+ $defaultSelect = $defaultValue ?? ($p['default'] ?? null);
+
+ // Für confirm: Default aus params[1] (default Parameter), falls noch keine Antwort vorhanden
+ $confirmDefault = null;
+ if ($method === 'confirm') {
+ $confirmDefault = $defaultValue !== null ? $defaultValue : ($p['default'] ?? false);
+ }
$validate = match ($method) {
- 'text', 'textarea', 'password' => $params[4] ?? null,
- 'select' => $params[5] ?? null,
- 'multiselect' => $params[6] ?? null,
+ 'text', 'textarea', 'password' => $p['validate'] ?? null,
+ 'select' => $p['validate'] ?? null,
+ 'multiselect' => $p['validate'] ?? null,
default => null,
};
@@ -585,36 +648,32 @@ protected function createFieldFromPrompt(string $promptId, string $method, array
$rules[] = 'in:'.implode(',', array_keys($options));
}
- if ($method === 'confirm') {
- $rules[] = 'boolean';
- }
-
$pushRules($rules, $validate);
return match ($method) {
'text' => TextInput::make($promptId)
->label($label)
- ->placeholder($params[1] ?? '')
- ->default($defaultValue ?? $params[2] ?? '')
+ ->placeholder($p['placeholder'] ?? '')
+ ->default($defaultValue ?? $p['default'] ?? '')
->rules($rules)
- ->hint($params[6] ?? null)
+ ->hint($p['hint'] ?? null)
->live(onBlur: false),
'textarea' => Textarea::make($promptId)
->label($label)
- ->placeholder($params[1] ?? '')
- ->default($defaultValue ?? $params[2] ?? '')
+ ->placeholder($p['placeholder'] ?? '')
+ ->default($defaultValue ?? $p['default'] ?? '')
->rules($rules)
->rows(5)
- ->hint($params[6] ?? null),
+ ->hint($p['hint'] ?? null),
'password' => TextInput::make($promptId)
->label($label)
->password()
- ->placeholder($params[1] ?? '')
+ ->placeholder($p['placeholder'] ?? '')
->default($defaultValue ?? '')
->rules($rules)
- ->hint($params[6] ?? null)
+ ->hint($p['hint'] ?? null)
->live(onBlur: false),
'select' => Select::make($promptId)
@@ -623,7 +682,7 @@ protected function createFieldFromPrompt(string $promptId, string $method, array
->default($defaultSelect !== null ? $defaultSelect : null)
->rules($rules)
->placeholder('Bitte wählen...')
- ->hint($params[4] ?? null)
+ ->hint($p['hint'] ?? null)
->live(onBlur: false),
'multiselect' => null,
@@ -631,12 +690,12 @@ protected function createFieldFromPrompt(string $promptId, string $method, array
'confirm' => Radio::make($promptId)
->label($label)
->options([
- true => 'Ja',
- false => 'Nein',
+ 'yes' => 'Ja',
+ 'no' => 'Nein',
])
- ->default($confirmDefault)
+ ->default($confirmDefault !== null ? ($confirmDefault ? 'yes' : 'no') : null)
->rules($rules)
- ->hint($params[6] ?? null)
+ ->hint($p['hint'] ?? null)
->live(onBlur: false),
default => null,
diff --git a/packages/prompts/src/Support/PromptParamsHelper.php b/packages/prompts/src/Support/PromptParamsHelper.php
new file mode 100644
index 000000000..58456bc89
--- /dev/null
+++ b/packages/prompts/src/Support/PromptParamsHelper.php
@@ -0,0 +1,98 @@
+ [
+ 'label' => $params[0] ?? '',
+ 'placeholder' => $params[1] ?? '',
+ 'default' => $params[2] ?? null,
+ 'required' => $params[3] ?? false,
+ 'validate' => $params[4] ?? null,
+ 'hint' => $params[5] ?? '',
+ 'transform' => $params[6] ?? null,
+ ],
+ 'textarea' => [
+ 'label' => $params[0] ?? '',
+ 'placeholder' => $params[1] ?? '',
+ 'required' => $params[2] ?? false,
+ 'validate' => $params[3] ?? null,
+ 'hint' => $params[4] ?? '',
+ 'transform' => $params[5] ?? null,
+ ],
+ 'password' => [
+ 'label' => $params[0] ?? '',
+ 'placeholder' => $params[1] ?? '',
+ 'required' => $params[2] ?? false,
+ 'validate' => $params[3] ?? null,
+ 'hint' => $params[4] ?? '',
+ 'transform' => $params[5] ?? null,
+ ],
+ 'confirm' => [
+ 'label' => $params[0] ?? '',
+ 'default' => $params[1] ?? false,
+ 'required' => $params[2] ?? false,
+ 'yes' => $params[3] ?? 'I accept',
+ 'no' => $params[4] ?? 'I decline',
+ 'hint' => $params[5] ?? '',
+ ],
+ 'select' => [
+ 'label' => $params[0] ?? '',
+ 'options' => $params[1] ?? [],
+ 'default' => $params[2] ?? null,
+ 'scroll' => $params[3] ?? null,
+ 'hint' => $params[4] ?? '',
+ 'validate' => $params[5] ?? null,
+ 'transform' => $params[6] ?? null,
+ ],
+ 'multiselect' => [
+ 'label' => $params[0] ?? '',
+ 'options' => $params[1] ?? [],
+ 'default' => $params[2] ?? [],
+ 'required' => $params[3] ?? false,
+ 'scroll' => $params[4] ?? null,
+ 'hint' => $params[5] ?? '',
+ 'validate' => $params[6] ?? null,
+ 'transform' => $params[7] ?? null,
+ ],
+ default => [],
+ };
+ }
+
+ /**
+ * Gibt einen einzelnen Parameter zurück.
+ *
+ * @param string $method Die Prompt-Methode
+ * @param array $params Die numerischen Parameter-Array
+ * @param string $paramName Der Name des Parameters (z.B. 'label', 'default')
+ * @param mixed $default Der Default-Wert, falls der Parameter nicht existiert
+ * @return mixed
+ */
+ public static function get(string $method, array $params, string $paramName, mixed $default = null): mixed
+ {
+ $extracted = self::extract($method, $params);
+
+ return $extracted[$paramName] ?? $default;
+ }
+}
+
From 35b48987c8f54191ab5b1768920641f618e329fc Mon Sep 17 00:00:00 2001
From: AzGasim <104441723+AzGasim@users.noreply.github.com>
Date: Tue, 16 Dec 2025 14:35:19 +0000
Subject: [PATCH 08/16] Fix styling
---
.../Components/RunCommandComponent.php | 9 ++++---
.../src/Support/PromptParamsHelper.php | 24 +++++++++----------
2 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/packages/prompts/src/Filament/Components/RunCommandComponent.php b/packages/prompts/src/Filament/Components/RunCommandComponent.php
index 9426adaea..24b047e11 100644
--- a/packages/prompts/src/Filament/Components/RunCommandComponent.php
+++ b/packages/prompts/src/Filament/Components/RunCommandComponent.php
@@ -185,7 +185,7 @@ protected function prefillPromptForm(array $prompt): void
$method = $prompt['method'] ?? '';
$params = $prompt['params'] ?? [];
$p = PromptParamsHelper::extract($method, $params);
-
+
// Wenn bereits eine Antwort vorhanden ist, diese verwenden
if (isset($this->answers[$promptId])) {
$value = $this->answers[$promptId];
@@ -200,6 +200,7 @@ protected function prefillPromptForm(array $prompt): void
}
}
$this->form->fill([$promptId => $value]);
+
return;
}
@@ -208,6 +209,7 @@ protected function prefillPromptForm(array $prompt): void
$default = $p['default'] ?? false; // default Parameter (bool)
$value = $default ? 'yes' : 'no';
$this->form->fill([$promptId => $value]);
+
return;
}
@@ -223,6 +225,7 @@ protected function prefillPromptForm(array $prompt): void
if (! empty($fillData)) {
$this->form->fill($fillData);
}
+
return;
}
@@ -317,7 +320,7 @@ public function submitPrompt(): void
$answer = false;
}
- if (($answer === null || $answer === '' || ($this->currentPrompt['method'] === 'multiselect' && ! is_array($answer))) && $this->currentPrompt['method'] !== 'confirm') {
+ if (($answer === null || $answer === '' || ($this->currentPrompt['method'] === 'multiselect' && ! is_array($answer))) && $this->currentPrompt['method'] !== 'confirm') {
try {
$data = $this->form->getState();
$answer = $data[$promptId] ?? null;
@@ -612,7 +615,7 @@ protected function createFieldFromPrompt(string $promptId, string $method, array
$defaultValue = $this->answers[$promptId] ?? null;
$options = $p['options'] ?? [];
$defaultSelect = $defaultValue ?? ($p['default'] ?? null);
-
+
// Für confirm: Default aus params[1] (default Parameter), falls noch keine Antwort vorhanden
$confirmDefault = null;
if ($method === 'confirm') {
diff --git a/packages/prompts/src/Support/PromptParamsHelper.php b/packages/prompts/src/Support/PromptParamsHelper.php
index 58456bc89..2276dd2f6 100644
--- a/packages/prompts/src/Support/PromptParamsHelper.php
+++ b/packages/prompts/src/Support/PromptParamsHelper.php
@@ -4,10 +4,10 @@
/**
* Helper-Klasse zum Zugriff auf Prompt-Parameter.
- *
+ *
* Statt direkt auf $params[0], $params[1] etc. zuzugreifen,
* verwenden wir diese Helper-Methoden, die die Parameter-Namen kennen.
- *
+ *
* So sind wir robuster gegen Änderungen in Laravel Prompts,
* solange die Parameter-Namen gleich bleiben.
*/
@@ -15,9 +15,9 @@ class PromptParamsHelper
{
/**
* Extrahiert die Parameter für eine Prompt-Methode als assoziatives Array.
- *
- * @param string $method Die Prompt-Methode (z.B. 'text', 'confirm', 'select')
- * @param array $params Die numerischen Parameter-Array
+ *
+ * @param string $method Die Prompt-Methode (z.B. 'text', 'confirm', 'select')
+ * @param array $params Die numerischen Parameter-Array
* @return array Assoziatives Array mit Parameternamen als Keys
*/
public static function extract(string $method, array $params): array
@@ -81,18 +81,16 @@ public static function extract(string $method, array $params): array
/**
* Gibt einen einzelnen Parameter zurück.
- *
- * @param string $method Die Prompt-Methode
- * @param array $params Die numerischen Parameter-Array
- * @param string $paramName Der Name des Parameters (z.B. 'label', 'default')
- * @param mixed $default Der Default-Wert, falls der Parameter nicht existiert
- * @return mixed
+ *
+ * @param string $method Die Prompt-Methode
+ * @param array $params Die numerischen Parameter-Array
+ * @param string $paramName Der Name des Parameters (z.B. 'label', 'default')
+ * @param mixed $default Der Default-Wert, falls der Parameter nicht existiert
*/
public static function get(string $method, array $params, string $paramName, mixed $default = null): mixed
{
$extracted = self::extract($method, $params);
-
+
return $extracted[$paramName] ?? $default;
}
}
-
From 1ad990dafc0b624af6fa93cdd12d60d98f8c43e5 Mon Sep 17 00:00:00 2001
From: Aziz Gasim <104441723+AzGasim@users.noreply.github.com>
Date: Tue, 16 Dec 2025 16:01:38 +0100
Subject: [PATCH 09/16] remove empty placeholder option from select prompts
---
.../prompts/src/Filament/Components/RunCommandComponent.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/prompts/src/Filament/Components/RunCommandComponent.php b/packages/prompts/src/Filament/Components/RunCommandComponent.php
index 24b047e11..b9f0f529a 100644
--- a/packages/prompts/src/Filament/Components/RunCommandComponent.php
+++ b/packages/prompts/src/Filament/Components/RunCommandComponent.php
@@ -684,7 +684,7 @@ protected function createFieldFromPrompt(string $promptId, string $method, array
->options($options)
->default($defaultSelect !== null ? $defaultSelect : null)
->rules($rules)
- ->placeholder('Bitte wählen...')
+ ->selectablePlaceholder(false)
->hint($p['hint'] ?? null)
->live(onBlur: false),
From 9eafce642995e7f796c76b59943cb23d5646f0c0 Mon Sep 17 00:00:00 2001
From: Aziz Gasim <104441723+AzGasim@users.noreply.github.com>
Date: Tue, 16 Dec 2025 16:26:51 +0100
Subject: [PATCH 10/16] translations
---
.../prompts/resources/lang/de/prompts.php | 34 +++++++++++++++++++
.../prompts/resources/lang/en/prompts.php | 34 +++++++++++++++++++
.../filament/components/run-command.blade.php | 14 ++++----
.../filament/pages/run-command.blade.php | 12 +++----
.../Components/RunCommandComponent.php | 30 ++++++++--------
.../src/Filament/Pages/RunCommandPage.php | 21 ++++++++++--
.../prompts/src/PromptsServiceProvider.php | 3 +-
packages/prompts/src/Support/FlowCommand.php | 14 ++++----
.../prompts/src/Support/PromptFlowRunner.php | 15 ++++----
.../src/Support/PromptParamsHelper.php | 28 +++++++--------
.../prompts/src/Support/WebPromptRuntime.php | 22 ++++++++----
11 files changed, 162 insertions(+), 65 deletions(-)
diff --git a/packages/prompts/resources/lang/de/prompts.php b/packages/prompts/resources/lang/de/prompts.php
index a5e40ea3a..57b5bc76f 100644
--- a/packages/prompts/resources/lang/de/prompts.php
+++ b/packages/prompts/resources/lang/de/prompts.php
@@ -3,4 +3,38 @@
return [
'prompt' => 'Prompt',
'prompts' => 'Prompts',
+
+ 'ui' => [
+ 'error_heading' => 'Fehler',
+ 'success_heading' => 'Command erfolgreich abgeschlossen!',
+ 'starting_heading' => 'Command wird gestartet...',
+ 'validation_title' => 'Bitte korrigieren:',
+ 'next_button' => 'Weiter',
+ 'output_heading' => 'Command Ausgabe',
+ 'confirm_yes' => 'Ja',
+ 'confirm_no' => 'Nein',
+ 'no_commands_available' => 'Keine Commands verfügbar. Bitte konfiguriere die erlaubten Commands in der',
+ 'command_label' => 'Command',
+ 'select_command_placeholder' => 'Bitte Command auswählen …',
+ 'commands_config_hint' => 'Nur Commands aus der Konfiguration sind hier sichtbar.',
+ 'start_command_button' => 'Command starten',
+ 'back_to_selection' => 'Zurück zur Command-Auswahl',
+ 'unknown_error' => 'Unbekannter Fehler',
+ 'navigation_label' => 'Command Runner',
+ 'navigation_group' => 'System',
+ ],
+
+ 'errors' => [
+ 'command_not_found' => 'Command nicht gefunden: :command',
+ 'step_not_found' => 'Step :step nicht gefunden auf Command :class',
+ ],
+
+ 'validation' => [
+ 'text_required' => 'Bitte „:label“ ausfüllen.',
+ 'multiselect_required' => 'Bitte mindestens eine Option wählen.',
+ 'multiselect_min' => 'Bitte mindestens eine Option wählen.',
+ 'select_required' => 'Bitte wählen Sie eine Option aus.',
+ 'select_in' => 'Bitte wählen Sie eine gültige Option aus.',
+ 'callable_invalid' => 'Ungültiger Wert.',
+ ],
];
diff --git a/packages/prompts/resources/lang/en/prompts.php b/packages/prompts/resources/lang/en/prompts.php
index a5e40ea3a..f8502bb35 100644
--- a/packages/prompts/resources/lang/en/prompts.php
+++ b/packages/prompts/resources/lang/en/prompts.php
@@ -3,4 +3,38 @@
return [
'prompt' => 'Prompt',
'prompts' => 'Prompts',
+
+ 'ui' => [
+ 'error_heading' => 'Error',
+ 'success_heading' => 'Command finished successfully!',
+ 'starting_heading' => 'Starting command...',
+ 'validation_title' => 'Please fix the following:',
+ 'next_button' => 'Next',
+ 'output_heading' => 'Command output',
+ 'confirm_yes' => 'Yes',
+ 'confirm_no' => 'No',
+ 'no_commands_available' => 'No commands available. Please configure allowed commands in',
+ 'command_label' => 'Command',
+ 'select_command_placeholder' => 'Please select a command …',
+ 'commands_config_hint' => 'Only commands from the configuration are visible here.',
+ 'start_command_button' => 'Start command',
+ 'back_to_selection' => 'Back to command selection',
+ 'unknown_error' => 'Unknown error',
+ 'navigation_label' => 'Command Runner',
+ 'navigation_group' => 'System',
+ ],
+
+ 'errors' => [
+ 'command_not_found' => 'Command not found: :command',
+ 'step_not_found' => 'Step :step not found on command :class',
+ ],
+
+ 'validation' => [
+ 'text_required' => 'Please fill in “:label”.',
+ 'multiselect_required' => 'Please select at least one option.',
+ 'multiselect_min' => 'Please select at least one option.',
+ 'select_required' => 'Please choose an option.',
+ 'select_in' => 'Please choose a valid option.',
+ 'callable_invalid' => 'Invalid value.',
+ ],
];
diff --git a/packages/prompts/resources/views/filament/components/run-command.blade.php b/packages/prompts/resources/views/filament/components/run-command.blade.php
index a4378823f..5c249c8c4 100644
--- a/packages/prompts/resources/views/filament/components/run-command.blade.php
+++ b/packages/prompts/resources/views/filament/components/run-command.blade.php
@@ -2,7 +2,7 @@
@if($error)
- Fehler
+ {{ __('moox-prompts::prompts.ui.error_heading') }}
{{ $error }}
@if($output)
@@ -16,7 +16,7 @@
@elseif($isComplete)
- Command erfolgreich abgeschlossen!
+ {{ __('moox-prompts::prompts.ui.success_heading') }}
@if($output)
- Validierungsfehler:
+
+ {{ __('moox-prompts::prompts.ui.validation_title') }}
+