feat(vorlagen): Versionierung, Unveraenderlichkeit und JSON-Austausch (#23)
This commit was merged in pull request #23.
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
# Austauschformat für Vorlagen
|
||||
|
||||
Eine Vorlage lässt sich als JSON-Datei exportieren und wieder einspielen. Diese Datei beschreibt
|
||||
das Format — für jemanden, der eine Vorlage von Hand schreiben oder ein Skript dafür bauen will.
|
||||
|
||||
Die Prüfung liegt in [`src/lib/vorlagen-format.ts`](../src/lib/vorlagen-format.ts), Export und
|
||||
Import in [`src/server/services/vorlagen-austausch.ts`](../src/server/services/vorlagen-austausch.ts).
|
||||
Beide benutzen dasselbe Schema: was hier steht, gilt für den Export, den Import und den Editor.
|
||||
|
||||
## Wozu das Format da ist
|
||||
|
||||
Eine exportierte Vorlage soll sich in eine **andere** Laufzettel-Installation einspielen lassen.
|
||||
Deshalb enthält das Dokument nur das, was eine Vorlage inhaltlich ausmacht: Kopfdaten, Variablen,
|
||||
Abschnitte, Schritte. Alles, was nur in der Quellinstallation eine Bedeutung hat, bleibt draußen.
|
||||
|
||||
**Nicht im Dokument enthalten:**
|
||||
|
||||
- **Kennungen** (`id`, `versionId`, `abschnittId`) — sie zeigen in der Zielinstallation ins Leere
|
||||
- **Zeitstempel** (`erstelltAm`, `geaendertAm`, `veroeffentlichtAm`) und der veröffentlichende
|
||||
Benutzer
|
||||
- **Kundenbezug** (`kundeId`) — eine eingespielte Vorlage gilt zunächst für alle Kunden; wer sie
|
||||
einem Kunden zuordnen will, tut das danach in der Oberfläche
|
||||
- **`position`** — die Reihenfolge steckt in der Reihenfolge der Listen. Zwei Wahrheiten über
|
||||
dieselbe Reihenfolge laufen früher oder später auseinander.
|
||||
- **Läufe**, Schrittergebnisse, Kommentare und das Protokoll — das ist Betriebsgeschehen, keine
|
||||
Vorlage
|
||||
- der Status der Version und das Archiv-Kennzeichen
|
||||
|
||||
## Beispiel
|
||||
|
||||
Das folgende Dokument ist vollständig: zwei Abschnitte, zwei Variablen und ein `BEFEHL`-Schritt mit
|
||||
Platzhaltern. **JSON kennt keine Kommentare** — die Zeilen mit `//` müssen vor dem Import raus.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
// Fassung des Austauschformats, derzeit immer 1.
|
||||
"format": 1,
|
||||
"name": "Onboarding Notebook",
|
||||
"beschreibung": "Neue Person, neues Gerät.",
|
||||
"kategorie": "Onboarding",
|
||||
"schlagworte": ["notebook", "eintritt"],
|
||||
// Nur zur Information: welche Version der Quelle exportiert wurde. Beim
|
||||
// Import wird der Wert gelesen und nicht übernommen.
|
||||
"version": 3,
|
||||
"struktur": {
|
||||
// Wird beim Start eines Laufs abgefragt und füllt die Platzhalter.
|
||||
"variablen": [
|
||||
{ "schluessel": "vorname", "beschriftung": "Vorname", "typ": "TEXT", "pflicht": true },
|
||||
{
|
||||
"schluessel": "abteilung",
|
||||
"beschriftung": "Abteilung",
|
||||
"typ": "AUSWAHL",
|
||||
"optionen": ["IT", "Vertrieb", "Buchhaltung"]
|
||||
}
|
||||
],
|
||||
// Die Reihenfolge dieser Liste ist die Reihenfolge in der Ansicht.
|
||||
"abschnitte": [
|
||||
{
|
||||
"titel": "01 · Vorbereitung",
|
||||
"schritte": [
|
||||
{ "titel": "Gerät auspacken", "pflicht": true },
|
||||
{
|
||||
"titel": "Seriennummer erfassen",
|
||||
"hilfetext": "Aufkleber auf der Unterseite. **Nicht** die Artikelnummer.",
|
||||
"typ": "TEXT",
|
||||
"pflicht": true,
|
||||
"konfig": { "mehrzeilig": false },
|
||||
"dokuUrl": "https://intern.example/seriennummern"
|
||||
},
|
||||
{
|
||||
"titel": "Arbeitsspeicher prüfen",
|
||||
"typ": "ZAHL",
|
||||
"konfig": { "min": 8, "max": 64, "einheit": "GB" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"titel": "02 · Konto",
|
||||
"schritte": [
|
||||
{
|
||||
"titel": "Benutzerkonto anlegen",
|
||||
"typ": "BEFEHL",
|
||||
"pflicht": true,
|
||||
// Die Platzhalter werden erst beim Abarbeiten ersetzt; hier stehen
|
||||
// sie wörtlich.
|
||||
"konfig": {
|
||||
"shell": "powershell",
|
||||
"befehl": "New-ADUser -Name \"{{vorname}}\" -Department \"{{abteilung}}\""
|
||||
}
|
||||
},
|
||||
{ "titel": "Eintrittsdatum hinterlegen", "typ": "DATUM" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Felder
|
||||
|
||||
### Kopf
|
||||
|
||||
| Feld | Pflicht | Bedeutung |
|
||||
| -------------- | -------- | ------------------------------------------------------------------------------- |
|
||||
| `format` | Pflicht | Fassung des Austauschformats. Derzeit `1`; ein anderer Wert wird abgewiesen. |
|
||||
| `name` | Pflicht | Name der Vorlage, nicht leer. Namen sind **nicht** eindeutig. |
|
||||
| `beschreibung` | optional | Ein bis zwei Sätze, was die Vorlage abdeckt. |
|
||||
| `kategorie` | optional | Freitext, gruppiert die Vorlagenliste (etwa `Onboarding`). |
|
||||
| `schlagworte` | optional | Liste nicht leerer Texte; Standard `[]`. |
|
||||
| `version` | optional | Versionsnummer der exportierten Quelle. Reine Information, siehe unten. |
|
||||
| `struktur` | Pflicht | Objekt mit `variablen` und `abschnitte`. |
|
||||
|
||||
### `struktur`
|
||||
|
||||
| Feld | Pflicht | Bedeutung |
|
||||
| ------------ | -------- | ---------------------------------------------------------- |
|
||||
| `variablen` | optional | Liste von Variablen in Abfragereihenfolge; Standard `[]`. |
|
||||
| `abschnitte` | optional | Liste von Abschnitten in Anzeigereihenfolge; Standard `[]`. |
|
||||
|
||||
### Variable
|
||||
|
||||
| Feld | Pflicht | Bedeutung |
|
||||
| -------------- | -------- | ------------------------------------------------------------------------- |
|
||||
| `schluessel` | Pflicht | Der Name im Platzhalter `{{schluessel}}`. Regeln siehe unten. |
|
||||
| `beschriftung` | Pflicht | Was im Startformular über dem Feld steht, nicht leer. |
|
||||
| `typ` | optional | `TEXT`, `ZAHL`, `AUSWAHL` oder `DATUM`; Standard `TEXT`. |
|
||||
| `optionen` | optional | Nur bei `AUSWAHL`: die Auswahlwerte in Anzeigereihenfolge; Standard `[]`. |
|
||||
| `pflicht` | optional | `true` verlangt eine Eingabe beim Start; Standard `false`. |
|
||||
|
||||
Ein Schlüssel darf in einem Dokument nur **einmal** vorkommen. Ein zweiter Eintrag mit demselben
|
||||
Schlüssel ist ein Fehler und kein stilles Überschreiben — sonst verschwände eine Variable, ohne
|
||||
dass es jemand merkt.
|
||||
|
||||
### Abschnitt
|
||||
|
||||
| Feld | Pflicht | Bedeutung |
|
||||
| ---------- | -------- | ----------------------------------------------------------------------------- |
|
||||
| `titel` | Pflicht | Überschrift, nicht leer. Üblich mit zweistelliger Nummer: `01 · Vorbereitung`. |
|
||||
| `schritte` | optional | Liste von Schritten in Abarbeitungsreihenfolge; Standard `[]`. |
|
||||
|
||||
### Schritt
|
||||
|
||||
| Feld | Pflicht | Bedeutung |
|
||||
| ----------- | -------- | -------------------------------------------------------------------------------- |
|
||||
| `titel` | Pflicht | Was zu tun ist, nicht leer. Darf Platzhalter enthalten. |
|
||||
| `hilfetext` | optional | Markdown, in der Abarbeitungsansicht ausklappbar. **Kein rohes HTML.** |
|
||||
| `typ` | optional | Einer der Schritt-Typen unten; Standard `HAKEN`. |
|
||||
| `pflicht` | optional | Pflichtschritte müssen vor dem Abschluss erledigt oder ausdrücklich als nicht zutreffend markiert sein; Standard `false`. |
|
||||
| `konfig` | optional | Typabhängige Einzelheiten; Standard `{}`. Siehe unten. |
|
||||
| `dokuUrl` | optional | Link auf eine Anleitung. Muss eine gültige Adresse sein. |
|
||||
|
||||
## Schritt-Typen und ihre `konfig`
|
||||
|
||||
| Typ | Was der Techniker tut | Schlüssel in `konfig` |
|
||||
| --------- | ------------------------------ | -------------------------------------------------------------- |
|
||||
| `HAKEN` | abhaken | keine |
|
||||
| `TEXT` | Text eintragen | `mehrzeilig` (`true`/`false`) |
|
||||
| `ZAHL` | Zahl eintragen | `min`, `max` (Zahlen), `einheit` (Text, höchstens 40 Zeichen) |
|
||||
| `AUSWAHL` | aus einer Liste wählen | `optionen` (Liste nicht leerer Texte, in Anzeigereihenfolge) |
|
||||
| `DATUM` | Datum wählen | keine |
|
||||
| `BEFEHL` | Befehl kopieren und ausführen | `shell` (`powershell`, `bash` oder `cmd`), `befehl` (Text) |
|
||||
|
||||
`min` und `max` sind Hinweise: ein Wert außerhalb wird markiert, nicht blockiert.
|
||||
|
||||
`konfig` wird **nachsichtig** geprüft. Unbekannte Schlüssel bleiben erhalten — eine Datei aus einer
|
||||
späteren Fassung verliert hier nichts, auch wenn diese Fassung die Schlüssel nicht kennt. Was die
|
||||
Anwendung kennt, wird geprüft: `shell` etwa akzeptiert nur die drei genannten Werte.
|
||||
|
||||
Ein Schlüssel, der nicht zum Typ passt, ist kein Fehler; er wird schlicht nicht beachtet. Wer den
|
||||
Typ eines Schritts ändert, räumt die `konfig` also besser selbst auf.
|
||||
|
||||
## Platzhalter und Variablenschlüssel
|
||||
|
||||
Ein Variablenschlüssel muss der Form `^[a-z][a-z0-9_]{0,39}$` entsprechen: Beginn mit einem
|
||||
Kleinbuchstaben, danach Kleinbuchstaben, Ziffern und Unterstrich, höchstens 40 Zeichen.
|
||||
`vorname`, `ad_gruppe` und `raum2` sind in Ordnung; `Vorname`, `mit-strich`, `2te_wahl` und ein
|
||||
leerer Schlüssel nicht.
|
||||
|
||||
Die Strenge hat einen Grund: der Schlüssel steht als `{{schluessel}}` in Titeln, Hilfetexten und
|
||||
Befehlen. Ein Platzhalter wird genau mit diesem Muster wiedererkannt. Wäre `Vor-Name` erlaubt,
|
||||
müsste der Suchausdruck Groß- und Kleinschreibung, Striche und Leerzeichen auseinanderhalten — und
|
||||
`{{Vor-Name}}` in einem PowerShell-Befehl wäre nicht mehr von dessen eigener Syntax zu trennen.
|
||||
Deshalb: ein Zeichensatz, eine Regel, keine Ausnahmen.
|
||||
|
||||
Geschweifte Klammern dürfen Leerzeichen enthalten (`{{ vorname }}` wird erkannt). Neben den
|
||||
Variablen der Vorlage gibt es eingebaute Platzhalter — `{{generiertes_passwort}}` etwa —, die nicht
|
||||
im Austauschdokument stehen und auch nirgends gespeichert werden.
|
||||
|
||||
## Was beim Import passiert
|
||||
|
||||
1. Das Dokument wird gegen das Schema geprüft. Schlägt das fehl, wird **nichts** geschrieben.
|
||||
2. Es entsteht eine **neue Vorlage** — der Import schreibt nie in eine bestehende hinein.
|
||||
3. Die Vorlage bekommt genau eine Version: `version = 1`, Status **`ENTWURF`**. Eine eingespielte
|
||||
Vorlage wird bewusst nicht sofort veröffentlicht; der Redakteur sieht sie sich erst an,
|
||||
ergänzt vielleicht etwas und veröffentlicht dann. Erst danach lässt sich ein Lauf daran starten.
|
||||
4. `position` wird aus der Reihenfolge der Listen abgeleitet (0, 1, 2 …).
|
||||
5. Das mitgeschickte `version`-Feld wird nicht übernommen. Eine Vorlage, die in der Quelle auf
|
||||
Version 7 stand, fängt hier wieder bei 1 an — die Versionsgeschichte der Quelle wandert nicht mit.
|
||||
6. Der Vorlagenname kommt so an, wie er im Dokument steht. Gibt es ihn schon, ist das kein Fehler
|
||||
und es wird nichts angehängt: Vorlagennamen sind nicht eindeutig.
|
||||
7. Alles geschieht in einer Transaktion. Ein Fehler auf halbem Weg hinterlässt keine halbe Vorlage.
|
||||
8. Ein Protokolleintrag mit der Aktion `Vorlage importiert` hält fest, wer die Vorlage eingespielt hat.
|
||||
|
||||
Wird das Dokument abgewiesen, nennt die Meldung die **erste** Beanstandung samt Pfad, etwa:
|
||||
|
||||
```
|
||||
struktur.abschnitte.1.schritte.2.titel: Ein Schritt braucht einen Titel.
|
||||
```
|
||||
|
||||
## Wie sich das Format ändern darf
|
||||
|
||||
Das Feld `format` ist die Fassung des Formats, nicht die der Anwendung. Es gilt:
|
||||
|
||||
- **Neue optionale Felder** und neue `konfig`-Schlüssel erhöhen `format` **nicht**. Alte Dateien
|
||||
bleiben gültig, und weil `konfig` nachsichtig geprüft wird, überleben neue Schlüssel auch einen
|
||||
Rundlauf durch eine ältere Fassung.
|
||||
- **Neue Schritt- und Variablentypen** erhöhen `format` ebenfalls nicht. Eine Fassung, die den Typ
|
||||
nicht kennt, weist die Datei allerdings ab, weil der Typ eine geprüfte Aufzählung ist.
|
||||
- Erst eine Änderung, die alte Dateien **unbrauchbar** macht — ein umbenanntes Pflichtfeld, eine
|
||||
andere Verschachtelung —, erhöht `format` auf `2`.
|
||||
- Ein unbekannter `format`-Wert wird mit einer klaren Meldung abgewiesen, nie geraten. Ein Import,
|
||||
der die Hälfte versteht und die andere Hälfte still verwirft, wäre schlimmer als ein Fehlschlag.
|
||||
|
||||
Die Zahl steht als `FORMAT_VERSION` in `src/lib/vorlagen-format.ts` an genau einer Stelle.
|
||||
Reference in New Issue
Block a user