Skip to content

Commit 4f2cf78

Browse files
committed
prompts wip
1 parent da631bd commit 4f2cf78

File tree

5 files changed

+270
-141
lines changed

5 files changed

+270
-141
lines changed

packages/prompts/README.md

Lines changed: 176 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,36 @@
11
# Moox Prompts
22

3-
CLI- und Web-kompatible Prompts für Laravel Artisan Commandsmit einem Flow, der im Browser Schritt für Schritt weiterläuft.
3+
CLI- and Web-compatible prompts for Laravel Artisan commandswith a flow that can continue step-by-step in the browser.
44

5-
## Wie muss ein Flow-Command aussehen?
5+
## What does a Flow Command look like?
66

7-
Damit ein Command sowohl in der CLI als auch im Web korrekt als Flow funktioniert, müssen nur diese Regeln erfüllt sein:
7+
To make a command work as a flow in both CLI and Web, you only need to follow these rules:
8+
9+
- **Extend `FlowCommand`**
810

9-
- **Von `FlowCommand` erben**
1011
```php
1112
use Moox\Prompts\Support\FlowCommand;
1213
use function Moox\Prompts\text;
1314
use function Moox\Prompts\select;
15+
```
1416

17+
```php
1518
class ProjectSetupCommand extends FlowCommand
1619
{
1720
protected $signature = 'prompts:project-setup';
18-
protected $description = 'Projekt Setup Wizard (CLI & Web)';
21+
protected $description = 'Project setup wizard (CLI & Web)';
1922
```
2023

21-
- **State als Properties ablegen** (werden im Web automatisch zwischen Steps gespeichert)
24+
- **Store state as public properties**
25+
(they are automatically persisted between steps in the web flow)
26+
2227
```php
2328
public ?string $environment = null;
2429
public ?string $projectName = null;
2530
```
2631

27-
- **Steps über `promptFlowSteps()` definieren** – Reihenfolge = Flow-Reihenfolge
32+
- **Define steps via `promptFlowSteps()`** – the array order **is the flow order**
33+
2834
```php
2935
public function promptFlowSteps(): array
3036
{
@@ -37,17 +43,18 @@ Damit ein Command sowohl in der CLI als auch im Web korrekt als Flow funktionier
3743
}
3844
```
3945

40-
- **Jeder Step ist eine `public function stepXyz(): void`** – idealerweise **ein Prompt pro Step**
46+
- **Each step is a `public function stepXyz(): void`** – ideally **one prompt per step**
47+
4148
```php
4249
public function stepIntro(): void
4350
{
44-
$this->info('=== Projekt Setup ===');
51+
$this->info('=== Project Setup ===');
4552
}
4653

4754
public function stepEnvironment(): void
4855
{
4956
$this->environment = select(
50-
label: 'Welche Umgebung konfigurierst du?',
57+
label: 'Which environment do you want to configure?',
5158
options: [
5259
'local' => 'Local',
5360
'staging' => 'Staging',
@@ -60,42 +67,44 @@ Damit ein Command sowohl in der CLI als auch im Web korrekt als Flow funktionier
6067
public function stepProjectName(): void
6168
{
6269
$this->projectName = text(
63-
label: 'Wie heißt dein Projekt?',
64-
placeholder: 'z.B. MyCoolApp',
70+
label: 'What is your project name?',
71+
placeholder: 'e.g. MyCoolApp',
6572
validate: 'required|min:3',
6673
required: true,
6774
);
6875
}
6976

7077
public function stepSummary(): void
7178
{
72-
$this->info('--- Zusammenfassung ---');
73-
$this->line('Projekt: '.$this->projectName);
79+
$this->info('--- Summary ---');
80+
$this->line('Project: '.$this->projectName);
7481
$this->line('Environment: '.$this->environment);
7582
}
7683
}
7784
```
7885

79-
- **Optionale Steps** kannst du einfach mit einem Guard am Anfang überspringen:
86+
- **Optional steps** can simply be skipped with a guard at the beginning:
87+
8088
```php
8189
public array $features = [];
8290

8391
public function stepLoggingLevel(): void
8492
{
8593
if (! in_array('logging', $this->features, true)) {
86-
return; // Step wird übersprungen
94+
return; // skip step
8795
}
8896

8997
// Prompt …
9098
}
9199
```
92100

93-
- **Andere Artisan-Commands aufrufen** – verwende im Flow immer `$this->call()` statt `Artisan::call()`, damit der Output auch im Web angezeigt wird:
101+
- **Calling other Artisan commands** – in a flow, always use `$this->call()` instead of `Artisan::call()`, so the output is also visible in the web UI:
102+
94103
```php
95104
public function stepPublishConfig(): void
96105
{
97106
$shouldPublish = confirm(
98-
label: 'Möchtest du die Config jetzt veröffentlichen?',
107+
label: 'Publish the config now?',
99108
default: true,
100109
);
101110

@@ -109,131 +118,201 @@ Damit ein Command sowohl in der CLI als auch im Web korrekt als Flow funktionier
109118
}
110119
```
111120

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.
121+
That’s all you need in the command – no special flow methods, no custom persistence.
122+
Everything else (CLI/Web differences, state, web UI) is handled by the package.
114123

115-
## Ausführung im Browser (Filament)
124+
## Running flows in the browser (Filament)
116125

117-
Nachdem du einen Flow-Command erstellt hast, kannst du ihn sowohl in der CLI als auch im Browser ausführen:
126+
Once you’ve created a flow command, you can run it in both CLI and browser.
118127

119-
### CLI-Ausführung
128+
### CLI
120129

121130
```bash
122131
php artisan prompts:project-setup
123132
```
124133

125-
Der Command läuft wie ein normaler Laravel Artisan Commandalle Prompts werden direkt im Terminal angezeigt.
134+
The command behaves like a normal Laravel Artisan commandall prompts are shown in the terminal.
126135

127-
### Web-Ausführung
136+
### Web
128137

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
138+
1. Open the Filament page **Run Command** (automatically added to navigation)
139+
2. Select your flow command from the list
140+
3. Click **“Start command”**
141+
4. The flow runs step by step in the browser:
142+
- Every step shows a prompt (text, select, multiselect, confirm, etc.)
143+
- After each step you see the step’s output
144+
- You can cancel any time with “Back to command selection”
145+
- After a successful run the button switches to “Start new command
137146

138-
**Wichtig:** Alle Commands, die im Web ausgeführt werden, werden automatisch in der Datenbank geloggt (siehe [Command Execution Logging](#command-execution-logging)).
147+
**Note:** All commands executed via the web UI are automatically logged in the database (see [Command Execution Logging](#command-execution-logging)).
139148

140-
## Wie und warum wird Reflection verwendet?
149+
## How and why reflection is used
141150

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.
151+
If you’re just writing commands, you don’t need to care about reflection.
152+
To understand what happens under the hood, here’s a short overview.
144153

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()` verwendenegal ob der Command per CLI oder im Browser läuft.
154+
- **Problem 1: Setting arguments & options in the web flow**
155+
Laravel stores arguments/options internally on a protected `$input` property of your command.
156+
In CLI mode the Artisan kernel takes care of this.
157+
In the web flow, we create fresh command instances – and need to set `$input` ourselves.
158+
That’s what `PromptFlowRunner::setCommandInput()` does via reflection:
159+
- finds the `input` property on your command object,
160+
- temporarily makes it accessible,
161+
- assigns the current `ArrayInput` instance.
162+
**Result:** In flow commands you can keep using `argument()` and `option()` normallyboth in CLI and in the browser.
154163

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:
164+
- **Problem 2: Remembering command state between web requests**
165+
In the web flow, your command runs across multiple HTTP requests. Without extra logic, properties like `$environment`, `$features`, `$projectName` would be lost between steps.
166+
`PromptFlowRunner` handles this with two internal methods:
158167
- `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`
168+
- uses reflection to read all non-static properties of your concrete command class
169+
- stores simple values (scalars, arrays, `null`) into `PromptFlowState::$context`
161170
- `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.
171+
- restores all stored values back onto the new command instance on the next request
172+
**Result:** For your code it feels like the same command instance keeps running – you don’t need your own persistence layer (cache, DB, session, …).
164173

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.
174+
- **Problem 3: Initializing package tools in the web context**
175+
Many packages using `Spatie\LaravelPackageTools` only register publishable resources (config, views, migrations, assets, …) in CLI context.
176+
`WebCommandRunner` uses reflection to access the internal `package` object and replay `publishes(...)` registrations for the web context.
177+
**Result:** Commands like `vendor:publish` work just as well in the browser as in CLI, even though Laravel is not running in console mode there.
169178

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:
179+
**Important:**
180+
Reflection is only used inside the package internals, not in your flow commands.
181+
Your commands remain normal Laravel commands – you only need to:
173182

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).
183+
- extend `FlowCommand`,
184+
- define properties for your state,
185+
- list steps in `promptFlowSteps()`,
186+
- implement `step*` methods (ideally one prompt per step).
178187

179-
Den Rest (Reflection, State, Web-Flow) übernimmt das Package für dich.
188+
The package takes care of the rest (reflection, state, web flow).
180189

181-
### Gibt es Alternativen ohne Reflection?
190+
### Are there alternatives without reflection?
182191

183-
Jatheoretisch könnten wir auf Reflection verzichten, aber das hätte Nachteile für dich als Nutzer:
192+
Yestechnically we could avoid reflection, but it would degrade the DX:
184193

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.
194+
- For **arguments & options** we’d need a custom API instead of `argument()/option()`, or force you to manage everything via properties/arrays. That’s less “Laravel-ish” and harder to learn.
195+
- For **state between steps** we could ask you to manually list all properties to persist (e.g. `flowContextKeys()`), or manage cache/DB/session yourself. That’s more boilerplate and error-prone.
196+
- For **Spatie Package Tools in the web** we’d either need changes in the Spatie package or manual configuration of all publishable paths – both would make setup more complex.
188197

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.
198+
That’s why we intentionally keep reflection encapsulated in the package and keep your command API as simple as possible.
190199

191200
## Command Execution Logging
192201

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.
202+
All commands executed via the web interface are automatically logged in the database.
203+
You can inspect them via the Filament resource **“Command Executions”**.
194204

195205
### Status
196206

197-
Jede Command-Ausführung hat einen der folgenden Status:
207+
Each execution has one of the following statuses:
208+
209+
- **`running`**: The command is currently running
210+
- **`completed`**: The command finished successfully
211+
- **`failed`**: The command failed with an error
212+
- **`cancelled`**: The command was cancelled by the user or aborted mid-flow
213+
214+
### Stored information
215+
216+
For each execution we store:
217+
218+
- **Basic information**: command name, description, status, timestamps
219+
- **Steps**: ordered list of all defined steps
220+
- **Step outputs**: output of each step (JSON)
221+
- **Context**: all command properties (e.g. `$environment`, `$projectName`, `$features`, …)
222+
- **Failure details**: for `failed` status – the error message and the step where it occurred (`failed_at_step`)
223+
- **Cancellation details**: for `cancelled` status – the step where cancellation happened (`cancelled_at_step`)
224+
- **User**: polymorphic relation to the user who started the command (`created_by`)
225+
226+
### Important: `step_outputs` vs `context`
198227

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
228+
It’s important to understand the difference between these two fields:
203229

204-
### Gespeicherte Informationen
230+
- **`step_outputs`**
231+
- Contains the **console output** of each step.
232+
- This is everything you print via `$this->info()`, `$this->line()`, `$this->warn()`, etc.
233+
- Example:
234+
235+
```php
236+
public function stepEnvironment(): void
237+
{
238+
$this->environment = select(...);
239+
$this->info("✅ Environment: {$this->environment}");
240+
}
241+
```
242+
243+
will result in e.g.:
244+
245+
```json
246+
"stepEnvironment": "✅ Environment: production\n"
247+
```
248+
249+
- **`context`**
250+
- Contains the **raw state** of your command – all public, non-static properties from your concrete command class.
251+
- This includes values returned from `text()`, `select()`, `multiselect()`, `confirm()`, etc.
252+
- Example:
253+
254+
```php
255+
public ?bool $publishConfig = null;
256+
257+
public function stepPublishConfigConfirm(): void
258+
{
259+
$this->publishConfig = confirm(
260+
label: 'Publish the config now?',
261+
default: true,
262+
);
263+
}
264+
```
265+
266+
will store in `context` something like:
267+
268+
```json
269+
{
270+
"publishConfig": true
271+
}
272+
```
273+
274+
but `step_outputs["stepPublishConfigConfirm"]` will be an **empty string**, because `confirm()` itself doesn’t print anything.
275+
276+
If you want to see the user’s choice in the **step output** as well, you can explicitly print it:
277+
278+
```php
279+
public function stepPublishConfigConfirm(): void
280+
{
281+
$this->publishConfig = confirm(
282+
label: 'Publish the config now?',
283+
default: true,
284+
);
285+
286+
$this->info('✅ Publish config: ' . ($this->publishConfig ? 'yes' : 'no'));
287+
}
288+
```
205289

206-
Für jede Ausführung werden folgende Daten gespeichert:
290+
This way you have:
207291

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`)
292+
- the decision in `context.publishConfig`, and
293+
- a readable line in `step_outputs.stepPublishConfigConfirm` for the history/inspector UI.
215294

216-
### Migration ausführen
295+
### Running the migration
217296

218-
Um das Logging zu aktivieren, führe die Migration aus:
297+
To enable logging, run:
219298

220299
```bash
221300
php artisan migrate
222301
```
223302

224-
Die Migration erstellt die Tabelle `command_executions` mit allen notwendigen Feldern.
303+
This creates the `command_executions` table with all necessary fields.
225304

226-
### Filament Resource
305+
### Filament resource
227306

228-
Die Filament-Resource "Command Executions" ist automatisch im Filament-Navigation-Menü verfügbar (falls aktiviert). Dort kannst du:
307+
The Filament resource **Command Executions** is automatically available in the Filament navigation (if enabled). There you can:
229308

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
309+
- inspect all past command executions,
310+
- filter by status,
311+
- see details per execution (steps, outputs, context, errors),
312+
- analyze failed or cancelled commands.
234313

235-
Die Resource zeigt auch an, bei welchem Step ein Command fehlgeschlagen (`failed_at_step`) oder abgebrochen (`cancelled_at_step`) wurde.
314+
The resource also shows which step a command **failed** on (`failed_at_step`) or where it was **cancelled** (`cancelled_at_step`).
236315

237316
## License
238317

239-
Siehe [LICENSE.md](LICENSE.md)
318+
See [LICENSE.md](LICENSE.md)

0 commit comments

Comments
 (0)