tim witter
Alle Beiträge

Blog

Projektgerüste ohne Überraschungen

Ein Projektgerüst hilft, wenn es erste Schritte wiederholbar macht. Vorsichtig werde ich, sobald Bequemlichkeit den Unterschied zwischen fehlenden Dateien und dem Ersetzen bestehender Entscheidungen verwischt.

Vor dem Erzeugen nachsehen

Zuerst erfasse ich die Zieldateien, lese Projektvorgaben und prüfe, was schon vorhanden ist. Meine Faustregel: Eine vorhandene Datei ist geleistete Arbeit, selbst wenn sie wie eine Standardvorlage aussieht. Eine Vorschau sollte geplante Pfade und Änderungen zeigen. Sie braucht einen zusätzlichen Schritt, ermöglicht aber, Kollisionen vor dem Schreiben zu erkennen.

Vor dem Erzeugen mache ich eine kurze Bestandsaufnahme: welche Pfade das Werkzeug anlegen, welche es ändern und welche es nur lesen will. Diese Liste ist schnell erstellt und schnell geprüft. Ein Pfad, den ich nicht erwartet habe, deutet meist darauf hin, dass die Annahmen der Vorlage von denen des Projekts abweichen.

Vorhandene Dateien verdienen einen zweiten Blick statt eines automatischen Ersatzes. Eine Datei kann eine Standardkopie, eine bewusste lokale Anpassung oder etwas dazwischen sein, an das sich niemand erinnert. Das lässt sich am Inhalt nicht immer erkennen, also gehe ich von der sichereren Lesart aus und frage nach. Eine Rückfrage kostet eine Nachricht; ein Überschreiben kann eine Rekonstruktion kosten.

scaffold-plan.tsTypeScript
interface PlannedFile {
  path: string;
  action: "create" | "skip";
  reason?: string;
}

// An existing file is skipped, never replaced by default.
export function planFiles(
  requested: readonly string[],
  existing: ReadonlySet<string>,
): PlannedFile[] {
  return requested.map((path) =>
    existing.has(path)
      ? { path, action: "skip", reason: "already present" }
      : { path, action: "create" },
  );
}

Änderungen gesondert entscheiden

Eine fehlende Konfiguration zu erzeugen und eine angepasste zu überschreiben sind verschiedene Vorgänge. Ich bevorzuge Werkzeuge, die bei Kollisionen zunächst stoppen und vor einem Ersatz eine ausdrückliche Entscheidung verlangen. Fehlt diese Möglichkeit, bereite ich den Vorschlag getrennt vor und prüfe den Unterschied. Stilles Überschreiben ist kein guter Tausch gegen einen schnelleren Start.

Hilfreich ist, wenn das Werkzeug Überspringen und Ersetzen als zwei Ergebnisse behandelt und nicht als eine Handlung mit Schalter. Ein übersprungener Pfad bleibt unberührt und sagt das auch; ein Ersatz benennt, was er überschreiben will. Sind beide Ergebnisse in der Vorschau sichtbar, prüfe ich sie mit derselben Aufmerksamkeit wie einen Unterschied, bevor geschrieben wird.

Ist ein Ersatz wirklich gewünscht, trenne ich trotzdem Vorbereitung und Schreiben. Ich erzeuge den vorgeschlagenen Inhalt an einer ungefährlichen Stelle, vergleiche ihn mit dem Vorhandenen und führe bedeutungstragende Unterschiede von Hand zusammen. Ein geprüfter Unterschied ist mehr wert als eine Erfolgsmeldung, die ich nicht gesehen habe.

scaffold-plan.jsonJSON
{
  "create": ["src/config.ts", "tests/config.test.ts"],
  "skip": [{ "path": "README.md", "reason": "already present" }],
  "replace": false
}

Auch die Grenze prüfen

Bricht die Erzeugung mittendrin ab, prüfe ich vor einem neuen Versuch den aktuellen Zustand. Ein zweiter Lauf darf einen unvollständigen ersten nicht verschlimmern. Danach vergleiche ich erwartete Dateien mit den tatsächlichen Änderungen und starte die vorgesehenen Prüfungen. Erfolg heißt auch, dass Unbeteiligtes unverändert blieb. Eine manuelle Zusammenführung benenne ich als solche.

Teilweise geschriebene Dateien sind der Grund, warum ich wiederholbare Schritte bevorzuge. Ein Schritt, der zweimal laufen kann, ohne beim zweiten Mal etwas anderes zu bewirken, lässt sich leicht erneut ausführen; ein Schritt, der anhängt oder hochzählt, nicht. Kann das Werkzeug das nicht zusichern, behandle ich den neuen Versuch als eigene Entscheidung und prüfe zuerst den Zustand. Diese Vorsicht kostet einen Blick ins Verzeichnis und verhindert eine Dopplung.

Zum Schluss vergleiche ich Zusage und Ergebnis. Die geplante Pfadliste und der tatsächliche Unterschied sollten übereinstimmen; jede Datei außerhalb der Liste verdient eine Erklärung. Danach starte ich die vorgesehenen Prüfungen. Dass neue Dateien funktionieren, ist nur die halbe Aussage; die andere Hälfte ist, dass Unbeteiligtes unverändert blieb.

scaffold.shShell
# Show the intended paths before writing anything.
scaffold --plan scaffold-plan.json --dry-run
# create: src/config.ts
# create: tests/config.test.ts
# skip:   README.md (already present)

# A second run still refuses a collision without an explicit option.
scaffold --plan scaffold-plan.json
# error: refusing to replace README.md without --replace