Skip to content

Commit ece1687

Browse files
authored
Feature/web prompts (#847)
* Add web interface support for Laravel Prompts * Fix styling * wip * Fix styling * wip * Fix styling * 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. * Fix styling * remove empty placeholder option from select prompts * translations * Add command execution logging * Fix styling * visible conditions * Fix styling * error message display * Fix styling
1 parent e0831ca commit ece1687

28 files changed

+3166
-214
lines changed

packages/core/src/Console/Commands/MooxInstaller.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
use Moox\Core\Console\Traits\SelectFilamentPanel;
1414
use Moox\Core\Services\PackageService;
1515

16-
use function Laravel\Prompts\multiselect;
17-
use function Laravel\Prompts\select;
16+
use function Moox\Prompts\multiselect;
17+
use function Moox\Prompts\select;
1818

1919
class MooxInstaller extends Command
2020
{

packages/prompts/README.md

Lines changed: 221 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,238 @@
11
# Moox Prompts
22

3-
CLI-kompatible Prompts für Laravel Artisan Commands.
3+
CLI- und Web-kompatible Prompts für Laravel Artisan Commands – mit einem Flow, der im Browser Schritt für Schritt weiterläuft.
4+
5+
## Wie muss ein Flow-Command aussehen?
6+
7+
Damit ein Command sowohl in der CLI als auch im Web korrekt als Flow funktioniert, müssen nur diese Regeln erfüllt sein:
8+
9+
- **Von `FlowCommand` erben**
10+
```php
11+
use Moox\Prompts\Support\FlowCommand;
12+
use function Moox\Prompts\text;
13+
use function Moox\Prompts\select;
14+
15+
class ProjectSetupCommand extends FlowCommand
16+
{
17+
protected $signature = 'prompts:project-setup';
18+
protected $description = 'Projekt Setup Wizard (CLI & Web)';
19+
```
20+
21+
- **State als Properties ablegen** (werden im Web automatisch zwischen Steps gespeichert)
22+
```php
23+
public ?string $environment = null;
24+
public ?string $projectName = null;
25+
```
26+
27+
- **Steps über `promptFlowSteps()` definieren** – Reihenfolge = Flow-Reihenfolge
28+
```php
29+
public function promptFlowSteps(): array
30+
{
31+
return [
32+
'stepIntro',
33+
'stepEnvironment',
34+
'stepProjectName',
35+
'stepSummary',
36+
];
37+
}
38+
```
39+
40+
- **Jeder Step ist eine `public function stepXyz(): void`** – idealerweise **ein Prompt pro Step**
41+
```php
42+
public function stepIntro(): void
43+
{
44+
$this->info('=== Projekt Setup ===');
45+
}
46+
47+
public function stepEnvironment(): void
48+
{
49+
$this->environment = select(
50+
label: 'Welche Umgebung konfigurierst du?',
51+
options: [
52+
'local' => 'Local',
53+
'staging' => 'Staging',
54+
'production' => 'Production',
55+
],
56+
default: 'local',
57+
);
58+
}
59+
60+
public function stepProjectName(): void
61+
{
62+
$this->projectName = text(
63+
label: 'Wie heißt dein Projekt?',
64+
placeholder: 'z.B. MyCoolApp',
65+
validate: 'required|min:3',
66+
required: true,
67+
);
68+
}
69+
70+
public function stepSummary(): void
71+
{
72+
$this->info('--- Zusammenfassung ---');
73+
$this->line('Projekt: '.$this->projectName);
74+
$this->line('Environment: '.$this->environment);
75+
}
76+
}
77+
```
78+
79+
- **Optionale Steps** kannst du einfach mit einem Guard am Anfang überspringen:
80+
```php
81+
public array $features = [];
82+
83+
public function stepLoggingLevel(): void
84+
{
85+
if (! in_array('logging', $this->features, true)) {
86+
return; // Step wird übersprungen
87+
}
88+
89+
// Prompt …
90+
}
91+
```
92+
93+
- **Andere Artisan-Commands aufrufen** – verwende im Flow immer `$this->call()` statt `Artisan::call()`, damit der Output auch im Web angezeigt wird:
94+
```php
95+
public function stepPublishConfig(): void
96+
{
97+
$shouldPublish = confirm(
98+
label: 'Möchtest du die Config jetzt veröffentlichen?',
99+
default: true,
100+
);
101+
102+
if (! $shouldPublish) {
103+
return;
104+
}
105+
106+
$this->call('vendor:publish', [
107+
'--tag' => 'moox-prompts-config',
108+
]);
109+
}
110+
```
111+
112+
Mehr ist im Command nicht nötig – keine speziellen Flow-Methoden, keine eigene Persistenz.
113+
Der Rest (CLI/Web-Unterschied, State, Web-Oberfläche) wird komplett vom Package übernommen.
114+
115+
## Ausführung im Browser (Filament)
116+
117+
Nachdem du einen Flow-Command erstellt hast, kannst du ihn sowohl in der CLI als auch im Browser ausführen:
118+
119+
### CLI-Ausführung
4120

5-
## Übersicht
121+
```bash
122+
php artisan prompts:project-setup
123+
```
124+
125+
Der Command läuft wie ein normaler Laravel Artisan Command – alle Prompts werden direkt im Terminal angezeigt.
126+
127+
### Web-Ausführung
128+
129+
1. Öffne die Filament-Seite "Run Command" (wird automatisch im Navigation-Menü angezeigt)
130+
2. Wähle einen Flow-Command aus der Liste
131+
3. Klicke auf "Command starten"
132+
4. Der Flow läuft Schritt für Schritt im Browser ab:
133+
- Jeder Step zeigt einen Prompt (Text-Input, Select, Multiselect, Confirm, etc.)
134+
- Nach jedem Step siehst du den Output des Steps
135+
- Du kannst jederzeit mit "Zurück zur Command-Auswahl" abbrechen
136+
- Nach erfolgreichem Abschluss wird der Button zu "Start new command" geändert
137+
138+
**Wichtig:** Alle Commands, die im Web ausgeführt werden, werden automatisch in der Datenbank geloggt (siehe [Command Execution Logging](#command-execution-logging)).
139+
140+
## Wie und warum wird Reflection verwendet?
141+
142+
Wenn du nur Commands schreibst, musst du dich nicht um Reflection kümmern.
143+
Damit du aber verstehst, was im Hintergrund passiert, hier eine kurze Erklärung.
144+
145+
- **Problem 1: Argumente & Optionen im Web setzen**
146+
Laravel speichert Argumente/Optionen intern in einem geschützten Property `$input` deines Commands.
147+
In der CLI kümmert sich der Artisan-Kernel darum, dieses Property zu setzen.
148+
Im Web-Flow erzeugen wir aber selbst neue Command-Instanzen – und müssen `$input` daher manuell setzen.
149+
Genau das macht `PromptFlowRunner::setCommandInput()` mit Reflection:
150+
- es findet das `input`-Property auf deinem Command-Objekt,
151+
- macht es kurz zugänglich,
152+
- und schreibt das aktuelle Input-Objekt hinein.
153+
**Ergebnis:** In Flow-Commands kannst du überall ganz normal `argument()` und `option()` verwenden – egal ob der Command per CLI oder im Browser läuft.
154+
155+
- **Problem 2: Command-State zwischen Web-Requests merken**
156+
Im Web besteht dein Flow aus mehreren HTTP-Requests. Ohne zusätzliche Logik wären Properties wie `$environment`, `$features`, `$projectName` im nächsten Step einfach weg.
157+
`PromptFlowRunner` löst das mit zwei internen Methoden:
158+
- `captureCommandContext($command, $state)`
159+
- liest per Reflection alle nicht-statischen Properties deiner konkreten Command-Klasse aus
160+
- speichert einfache Werte (Scalars, Arrays, `null`) im `PromptFlowState::$context`
161+
- `restoreCommandContext($command, $state)`
162+
- setzt beim nächsten Request alle gespeicherten Werte wieder zurück auf das neue Command-Objekt
163+
**Ergebnis:** Für deinen Code fühlt es sich so an, als würde derselbe Command einfach weiterlaufen – du musst keine eigene Persistenz (Cache, Datenbank, Session, …) schreiben.
164+
165+
- **Problem 3: Package-Tools im Web initialisieren**
166+
Viele Packages, die `Spatie\LaravelPackageTools` verwenden, registrieren ihre publishable Ressourcen (Config, Views, Migrations, Assets, …) nur im CLI-Kontext.
167+
`WebCommandRunner` verwendet Reflection, um intern an das `package`-Objekt eines solchen Service Providers zu kommen und die `publishes(...)`-Registrierung auch im Web nachzuholen.
168+
**Ergebnis:** Befehle wie `vendor:publish` funktionieren im Browser genauso zuverlässig wie in der CLI, obwohl Laravel dort eigentlich nicht im Console-Modus läuft.
169+
170+
**Wichtig:**
171+
Reflection wird nur in diesen internen Klassen des Packages verwendet, nicht in deinen Commands.
172+
Deine Commands bleiben normale Laravel-Commands – du musst nur:
173+
174+
- von `FlowCommand` erben,
175+
- Properties für den State definieren,
176+
- Steps in `promptFlowSteps()` auflisten,
177+
- `step*`-Methoden schreiben (am besten ein Prompt pro Step).
6178

7-
Dieses Package bietet eine einfache Proxy-Implementierung für Laravel Prompts. Es ermöglicht es, die gleichen Helper-Funktionen wie Laravel Prompts zu verwenden, mit der Möglichkeit, später Web-Funktionalität hinzuzufügen.
179+
Den Rest (Reflection, State, Web-Flow) übernimmt das Package für dich.
8180

9-
## Features
181+
### Gibt es Alternativen ohne Reflection?
10182

11-
- ✅ Alle Laravel Prompt-Typen unterstützt (`text`, `select`, `multiselect`, `confirm`, etc.)
12-
- ✅ Identische API wie Laravel Prompts
183+
Ja – theoretisch könnten wir auf Reflection verzichten, aber das hätte Nachteile für dich als Nutzer:
13184

14-
## Installation
185+
- Für **Argumente & Optionen** könnten wir eine eigene API einführen (statt `argument()/option()`), oder erzwingen, dass du alles manuell über Properties/Arrays verwaltest. Das wäre weniger “Laravel-typisch” und schwerer zu verstehen.
186+
- Für den **Command-State zwischen Steps** könnten wir dich z.B. eine Methode wie `flowContextKeys()` implementieren lassen, in der du alle zu speichernden Properties auflistest, oder dich zwingen, selbst Cache/DB/Session zu benutzen. Das wäre mehr Boilerplate und eine zusätzliche Fehlerquelle.
187+
- Für **Spatie Package Tools im Web** bräuchten wir entweder Änderungen im Spatie-Package selbst oder eine eigene, manuelle Konfiguration aller publishbaren Pfade – beides würde die Einrichtung deutlich komplizierter machen.
188+
189+
Aus diesen Gründen kapseln wir die Reflection-Nutzung bewusst im Package und halten die API für deine Commands so einfach wie möglich.
190+
191+
## Command Execution Logging
192+
193+
Alle Commands, die über das Web-Interface ausgeführt werden, werden automatisch in der Datenbank geloggt. Du kannst die Ausführungen in der Filament-Resource "Command Executions" einsehen.
194+
195+
### Status
196+
197+
Jede Command-Ausführung hat einen der folgenden Status:
198+
199+
- **`running`**: Der Command läuft gerade
200+
- **`completed`**: Der Command wurde erfolgreich abgeschlossen
201+
- **`failed`**: Der Command ist mit einem Fehler fehlgeschlagen
202+
- **`cancelled`**: Der Command wurde vom Benutzer abgebrochen
203+
204+
### Gespeicherte Informationen
205+
206+
Für jede Ausführung werden folgende Daten gespeichert:
207+
208+
- **Basis-Informationen**: Command-Name, Beschreibung, Status, Zeitstempel
209+
- **Steps**: Liste aller Steps, die ausgeführt wurden
210+
- **Step-Outputs**: Output jedes einzelnen Steps (als JSON)
211+
- **Context**: Alle Command-Properties (z.B. `$environment`, `$projectName`, etc.)
212+
- **Fehler-Informationen**: Bei `failed` Status: Fehlermeldung und der Step, bei dem der Fehler aufgetreten ist (`failed_at_step`)
213+
- **Abbruch-Informationen**: Bei `cancelled` Status: Der Step, bei dem abgebrochen wurde (`cancelled_at_step`)
214+
- **Benutzer**: Polymorphe Beziehung zu dem Benutzer, der den Command gestartet hat (`created_by`)
215+
216+
### Migration ausführen
217+
218+
Um das Logging zu aktivieren, führe die Migration aus:
15219

16220
```bash
17-
composer require moox/prompts
221+
php artisan migrate
18222
```
19223

20-
## Verwendung
21-
22-
### In Commands
23-
24-
Verwende die gleichen Helper-Funktionen wie in Laravel Prompts:
25-
26-
```php
27-
use function Moox\Prompts\text;
28-
use function Moox\Prompts\select;
29-
use function Moox\Prompts\confirm;
30-
use function Moox\Prompts\form;
31-
32-
public function handle()
33-
{
34-
// Einzelne Prompts
35-
$name = text('What is your name?');
36-
$package = select('Which package?', ['moox/core', 'moox/user']);
37-
$confirm = confirm('Are you sure?');
38-
39-
// FormBuilder
40-
$result = form()
41-
->text('Name?')
42-
->select('Package?', ['moox/core', 'moox/user'])
43-
->submit();
44-
45-
// Command-Logik...
46-
}
47-
```
224+
Die Migration erstellt die Tabelle `command_executions` mit allen notwendigen Feldern.
225+
226+
### Filament Resource
48227

49-
## Architektur
228+
Die Filament-Resource "Command Executions" ist automatisch im Filament-Navigation-Menü verfügbar (falls aktiviert). Dort kannst du:
50229

51-
Das Package besteht aus:
230+
- Alle vergangenen Command-Ausführungen einsehen
231+
- Nach Status filtern
232+
- Details zu jeder Ausführung ansehen (Steps, Outputs, Context, etc.)
233+
- Fehlgeschlagene oder abgebrochene Commands analysieren
52234

53-
- **PromptRuntime**: Interface für Prompt-Implementierungen
54-
- **CliPromptRuntime**: CLI-Implementierung (delegiert an Laravel Prompts)
55-
- **functions.php**: Globale Helper-Funktionen
56-
- **PromptsServiceProvider**: Registriert Services
235+
Die Resource zeigt auch an, bei welchem Step ein Command fehlgeschlagen (`failed_at_step`) oder abgebrochen (`cancelled_at_step`) wurde.
57236

58237
## License
59238

packages/prompts/config/prompts.php

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,55 +2,53 @@
22

33
/*
44
|--------------------------------------------------------------------------
5-
| Moox Configuration
5+
| Prompts Configuration
66
|--------------------------------------------------------------------------
77
|
8-
| This configuration file uses translatable strings. If you want to
9-
| translate the strings, you can do so in the language files
10-
| published from moox_core. Example:
11-
|
12-
| 'trans//core::core.all',
13-
| loads from common.php
14-
| outputs 'All'
8+
| This configuration file defines which Artisan commands are allowed to be
9+
| executed through the web interface using prompts.
1510
|
1611
*/
12+
1713
return [
1814

19-
'readonly' => false,
15+
/*
16+
|--------------------------------------------------------------------------
17+
| Allowed Commands
18+
|--------------------------------------------------------------------------
19+
|
20+
| List of Artisan command names that are allowed to be executed through
21+
| the web interface. Only commands listed here will be available in the
22+
| Command Runner page.
23+
|
24+
| Example:
25+
| 'allowed_commands' => [
26+
| 'prompts:test-flow',
27+
| 'prompts:test-web',
28+
| ],
29+
|
30+
*/
2031

21-
'resources' => [
22-
'item' => [
23-
'single' => 'trans//item::item.item',
24-
'plural' => 'trans//item::item.items',
25-
'tabs' => [
26-
'all' => [
27-
'label' => 'trans//core::core.all',
28-
'icon' => 'gmdi-filter-list',
29-
'query' => [
30-
[
31-
'field' => 'title',
32-
'operator' => '!=',
33-
'value' => null,
34-
],
35-
],
36-
],
37-
],
38-
],
32+
'allowed_commands' => [
33+
'prompts:project-setup',
34+
'prompts:test-failed',
35+
// Add more commands here as needed
3936
],
4037

41-
'relations' => [],
42-
4338
/*
4439
|--------------------------------------------------------------------------
45-
| Navigation
40+
| Navigation Group
4641
|--------------------------------------------------------------------------
4742
|
48-
| The navigation group and sort of the Resource,
49-
| and if the panel is enabled.
43+
| The navigation group where the Command Runner and Command Executions
44+
| will appear in the Filament navigation. Common options:
45+
| - 'System' (default)
46+
| - 'Jobs'
47+
| - 'Tools'
48+
| - null (no group)
5049
|
5150
*/
52-
'auth' => [
53-
'user' => 'Moox\\DevTools\\Models\\TestUser',
54-
],
55-
'navigation_group' => 'DEV',
51+
52+
'navigation_group' => 'System',
53+
5654
];

0 commit comments

Comments
 (0)