Zum Inhalt

Konzept: Weitere Core/Client Erweiterungs-Patterns

Stand: März 2026
Status: Entwurf

Bereits umgesetzt

Bereich Pattern Beschreibung
Flutter/Dart Git-Dependency + ClientConfig Core-Packages via pubspec.yaml, 50+ Override-Punkte
Firestore Rules _core + _extra Merge Core-Regeln + Client-Erweiterungen, gemerged beim Deploy
Storage Rules _core + _extra Merge Identisch zu Firestore Rules
Firestore Indexes _core + _extra Merge JSON-Array-Merge via Node.js
Cloud Functions Merge-at-Deploy Core + Client Jobs/Connectors/Triggers in einer Codebase

Vorschlag 1: Unified Deploy Script (deploy_all.sh)

Problem

Aktuell existieren 3 separate Deploy-Scripts im Client-Repo:

deploy_rules.sh       → Firebase Rules + Indexes
deploy_functions.sh   → Cloud Functions (Merge-at-Deploy)
deploy_cors.sh        → CORS auf Storage Bucket

Plus Hosting-Deploy in auto-deploy.yml (CI) oder manuell via Firebase CLI. Bei einem vollständigen Redeploy muss man alles einzeln ausführen und die Reihenfolge kennen.

Lösung

Ein deploy_all.sh das alle Schritte orchestriert:

./deploy_all.sh <project-id> [--skip-hosting] [--skip-functions] [--skip-rules] [--skip-cors]

Ablauf:

1. Rules deployen        (deploy_rules.sh)
2. CORS deployen         (deploy_cors.sh)
3. Functions deployen    (deploy_functions.sh)
4. Hosting deployen      (flutter build web + firebase deploy --only hosting)

Vorteile: - Ein Befehl für alles → weniger Fehler - Richtige Reihenfolge garantiert (Rules vor Functions, da Triggers auf Rules-Änderungen reagieren können) - Skip-Flags für partielle Deploys - Ideal nach Core-Update: ./deploy_all.sh gemuesebau-steiner-dev

Aufwand: Gering (~80 Zeilen Bash)
Priorität: ⭐⭐⭐ Hoch


Vorschlag 2: CI/CD Workflow als Reusable Workflow

Problem

auto-deploy.yml ist aktuell eine statische Kopie in jedem Client-Repo. Wenn der Core eine CI-Verbesserung macht (z.B. neuer Build-Step, Sentry-Sourcemaps, Caching-Optimierung), müssen alle Client-Repos manuell aktualisiert werden — exakt das Problem, das wir bei Functions mit Merge-at-Deploy gelöst haben.

Ist-Zustand

Core:   .github/workflows/web-core-deployment.yml    (baut Core-Demo-Apps)
        .github/workflows/notify-clients.yml         (triggert Clients via repository_dispatch)

Client: .github/workflows/auto-deploy.yml            (STATISCHE KOPIE, 80+ Zeilen)

Lösung: Reusable Workflow im Core

Core stellt einen gemeinsamen Workflow bereit:

# Core: .github/workflows/client-deploy-reusable.yml
name: Client Deploy (Reusable)

on:
  workflow_call:
    inputs:
      environment:
        type: string
        default: 'production'
      deploy_hosting:
        type: boolean
        default: true
      deploy_functions:
        type: boolean
        default: true
      deploy_rules:
        type: boolean
        default: true
      flutter_build_args:
        type: string
        default: ''
    secrets:
      FIREBASE_SA:
        required: true
      SENTRY_DSN:
        required: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Checkout Core
        uses: actions/checkout@v4
        with:
          repository: Tech-Schuppen/easySale
          path: _core
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with: { channel: stable, cache: true }

      # ... Build + Deploy Steps (zentral gepflegt)

Client nutzt den Workflow mit 10 Zeilen:

# Client: .github/workflows/auto-deploy.yml
name: Build & Deploy
on:
  repository_dispatch:
    types: [core-updated]
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  deploy:
    uses: Tech-Schuppen/easySale/.github/workflows/client-deploy-reusable.yml@main
    with:
      environment: production
      flutter_build_args: '--dart-define=CUSTOM_FLAG=true'
    secrets:
      FIREBASE_SA: ${{ secrets.FIREBASE_SA_PROD }}
      SENTRY_DSN: ${{ secrets.SENTRY_DSN }}

Vorteile: - CI-Verbesserungen wirken automatisch für alle Clients - Client-spezifische Parameter via inputs - Weniger Maintenance pro Client-Repo - Einheitliche Build-Qualität

Einschränkung: Das Repo Tech-Schuppen/easySale muss für Client-Repos lesbar sein (Enterprise/Org-level, oder Public Repo für Workflow). Alternativ: Workflow als separates Public Repo.

Aufwand: Mittel (Reusable Workflow schreiben + bestehende Client-Workflows migrieren)
Priorität: ⭐⭐⭐ Hoch


Vorschlag 3: CORS _core + _extra Merge

Problem

Aktuell pflegt jeder Client seine CORS-Konfiguration komplett selbst (firebase/cors.dev.json, firebase/cors.prod.json). Wenn Core neue Response-Headers oder Methoden hinzufügt, müssen alle Clients manuell aktualisiert werden.

Ist-Zustand

// Client cors.dev.json — vollständig self-managed
[{
  "origin": ["http://localhost:8080", "https://gemuesebau-steiner-dev.web.app", ...],
  "method": ["GET", "HEAD", "PUT", "POST", "DELETE", "OPTIONS"],
  "responseHeader": ["Content-Type", "Authorization", "X-Requested-With"],
  "maxAgeSeconds": 3600
}]

Lösung: Merge-Pattern wie bei Rules

Client-Struktur:

firebase/
  cors_core.dev.json        ← Wird beim Core-Update automatisch aktualisiert
  cors_core.prod.json       ← Wird beim Core-Update automatisch aktualisiert
  cors_extra.dev.json        ← Client-spezifische Erweiterungen
  cors_extra.prod.json       ← Client-spezifische Erweiterungen
  cors.dev.json              ← GENERIERT (gitignored)
  cors.prod.json             ← GENERIERT (gitignored)

Core liefert Standard-Config:

// cors_core.dev.json
[{
  "origin": ["http://localhost:8080", "http://localhost:8081"],
  "method": ["GET", "HEAD", "PUT", "POST", "DELETE", "OPTIONS"],
  "responseHeader": ["Content-Type", "Authorization", "X-Requested-With", "Accept", "Origin"],
  "maxAgeSeconds": 3600
}]

Client fügt nur eigene Origins hinzu:

// cors_extra.dev.json
{
  "extraOrigins": [
    "https://gemuesebau-steiner-dev.web.app",
    "https://gemuesebau-steiner-dev.firebaseapp.com"
  ]
}

Merge-Logik (in deploy_cors.sh):

# Core-Origins + Client-Origins zusammenführen
node -e "
  const core = require('./$CORS_CORE');
  const extra = require('./$CORS_EXTRA');
  core[0].origin = [...core[0].origin, ...(extra.extraOrigins || [])];
  if (extra.extraHeaders) core[0].responseHeader.push(...extra.extraHeaders);
  console.log(JSON.stringify(core, null, 2));
" > "$CORS_OUTPUT"

Vorteile: - Core kann Methods/Headers aktualisieren → alle Clients bekommen es - Client muss nur seine eigenen Hosting-URLs pflegen - Gleicher Pattern wie Rules → vertrautes Konzept

Aufwand: Gering (~30 Zeilen Merge-Logik)
Priorität: ⭐⭐ Mittel


Vorschlag 4: Feature-Flag Defaults aus dem Core

Problem

firebase_config_*.json (pro Client/Environment) enthält Feature-Flags. Aktuell definiert jeder Client alle Features selbst. Wenn Core ein neues Feature einführt (z.B. advanced_search: true), muss jeder Client manuell aktualisiert werden.

Ist-Zustand

// Client: erp/assets/firebase_config/firebase_config_development.json
{
  "apiKey": "...",
  "projectId": "gemuesebau-steiner-dev",
  "storageBucket": "...",
  "cloudFunctionsRegion": "europe-west1"
}

Features werden aktuell über ClientConfig.features in Dart gesetzt — kein zentraler Config-Mechanismus.

Lösung: Feature-Defaults vom Core + Client-Overrides

Core stellt Default-Features bereit:

// core/shared/assets/default_features.json
{
  "order_export": true,
  "article_import": true,
  "customer_import": true,
  "advanced_search": true,
  "stock_management": true,
  "multi_warehouse": false,
  "invoice_generation": false,
  "shop_system": true
}

Client überschreibt nur Abweichungen:

// Client: erp/assets/firebase_config/feature_overrides.json
{
  "multi_warehouse": true,
  "shop_system": false,
  "custom_client_feature": true
}

Merge-Logik (im Dart-Code beim App-Start):

// In AppConfig oder ClientConfig
Map<String, bool> get mergedFeatures {
  final defaults = json.decode(rootBundle.loadString('assets/default_features.json'));
  final overrides = json.decode(rootBundle.loadString('assets/feature_overrides.json'));
  return {...defaults, ...overrides};  // Client gewinnt bei Konflikten
}

Vorteile: - Neue Core-Features sind sofort für alle Clients verfügbar - Clients aktivieren/deaktivieren nur was sie brauchen - Ein Blick auf feature_overrides.json zeigt sofort Client-Anpassungen - Keine Dart-Code-Änderung im Client nötig für Feature-Toggles

Aufwand: Gering (eine JSON-Datei im Core, Merge-Logik ~20 Zeilen Dart)
Priorität: ⭐⭐ Mittel


Vorschlag 5: Hosting Security Headers _core + _extra

Problem

firebase.json im Client enthält Hosting-Konfiguration mit Security-Headers (CSP, HSTS, X-Frame-Options etc.). Wenn Core einen Header aktualisiert (z.B. neue CSP-Directive), müssen alle Clients ihre firebase.json manuell anpassen.

Ist-Zustand

// Client: firebase/firebase.json
{
  "hosting": {
    "public": "../erp/build/web",
    "rewrites": [{"source": "**", "destination": "/index.html"}]
  }
}

Aktuell hat der Client minimale Headers. Core hat umfangreichere.

Lösung: firebase.json Generierung beim Deploy

Core liefert Standard-Hosting-Config:

// firebase_hosting_core.json
{
  "headers": [
    {
      "source": "**",
      "headers": [
        {"key": "X-Frame-Options", "value": "DENY"},
        {"key": "X-Content-Type-Options", "value": "nosniff"},
        {"key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains"},
        {"key": "Referrer-Policy", "value": "strict-origin-when-cross-origin"},
        {"key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()"}
      ]
    }
  ]
}

Client kann Headers ergänzen oder überschreiben:

// firebase_hosting_extra.json
{
  "extraHeaders": [
    {"key": "X-Custom-Header", "value": "gemuesebau-steiner"}
  ],
  "overrideHeaders": {
    "Permissions-Policy": "camera=(self), microphone=(), geolocation=()"
  }
}

Deploy-Script merged Core + Extra → finale firebase.json.

Vorteile: - Security-Best-Practices zentral im Core - Client kann Client-spezifische Headers ergänzen (z.B. CSP für externe Dienste) - Neue Security-Headers wirken automatisch

Aufwand: Mittel (JSON-Merge-Logik + Deploy-Script-Anpassung)
Priorität: ⭐ Niedrig (erst relevant wenn Core komplexere Headers einführt)


Vorschlag 6: Cloud Tasks Queue Erweiterung

Problem

Aktuell erstellt setup_cloud_tasks_queue.sh nur die hartcodierte order-export-queue. Wenn ein Client einen Custom-Job hat, der eine eigene Queue braucht, gibt es keinen strukturierten Weg.

Lösung: Queue-Definition per Konfigurationsdatei

Client definiert benötigte Queues:

// Client: firebase/cloud_tasks_queues.json
[
  {
    "name": "custom-sync-queue",
    "maxConcurrentDispatches": 5,
    "maxDispatchesPerSecond": 2,
    "maxAttempts": 3
  }
]

Core-Queue (order-export-queue) wird immer erstellt. Client-Queues nur wenn Datei existiert.

Deploy-Script:

# Immer: Core-Queue
gcloud tasks queues create order-export-queue ...

# Optional: Client-Queues aus cloud_tasks_queues.json
if [[ -f "$CLIENT_DIR/firebase/cloud_tasks_queues.json" ]]; then
  # JSON parsen und Queues erstellen
fi

Aufwand: Gering (~40 Zeilen Bash)
Priorität: ⭐ Niedrig (erst relevant wenn Clients Custom-Queues brauchen)


Priorisierte Roadmap

Prio Vorschlag Aufwand Nutzen
1 Unified Deploy Script Gering Sofort nutzbar, weniger Fehler
2 CI/CD Reusable Workflow Mittel Keine manuelle CI-Pflege pro Client
3 CORS Merge Gering Konsistente CORS-Updates
4 Feature-Flag Defaults Gering Neue Features automatisch verfügbar
5 Hosting Headers Merge Mittel Security-Updates automatisch
6 Cloud Tasks Extension Gering Nur bei Bedarf

Empfehlung

Sofort umsetzen (Quick Wins): - Vorschlag 1 (deploy_all.sh) — am meisten praktischer Nutzen im Alltag - Vorschlag 3 (CORS Merge) — gleicher bewährter Pattern wie Rules

Nächste Iteration: - Vorschlag 2 (Reusable Workflow) — größter langfristiger Wartungs-Vorteil - Vorschlag 4 (Feature-Flag Defaults) — sinnvoll sobald Feature-Anzahl wächst

Bei Bedarf: - Vorschlag 5 (Hosting Headers) + Vorschlag 6 (Cloud Tasks)