From b4b1dde48451f2539b3da981ef70a3346cca8ad3 Mon Sep 17 00:00:00 2001 From: smueller Date: Thu, 28 May 2026 10:12:27 +0200 Subject: [PATCH] Refactor production build process: Removed outdated PowerShell and Bash scripts for publishing to main, consolidating build instructions in README.md. Updated production build steps to include automatic obfuscation and minification of assets, enhancing deployment efficiency. --- .github/workflows/obfuscate-main.yml | 44 +++ README.md | 23 +- .../obfuscate_release.cpython-314.pyc | Bin 0 -> 13123 bytes scripts/obfuscate_release.py | 337 ++++++++++++++++++ scripts/publish-to-main.ps1 | 185 ---------- scripts/publish-to-main.sh | 187 ---------- scripts/run-build.ps1 | 46 --- scripts/run-build.sh | 58 --- 8 files changed, 391 insertions(+), 489 deletions(-) create mode 100644 .github/workflows/obfuscate-main.yml create mode 100644 scripts/__pycache__/obfuscate_release.cpython-314.pyc create mode 100644 scripts/obfuscate_release.py delete mode 100644 scripts/publish-to-main.ps1 delete mode 100644 scripts/publish-to-main.sh delete mode 100644 scripts/run-build.ps1 delete mode 100644 scripts/run-build.sh diff --git a/.github/workflows/obfuscate-main.yml b/.github/workflows/obfuscate-main.yml new file mode 100644 index 0000000..0c1b10e --- /dev/null +++ b/.github/workflows/obfuscate-main.yml @@ -0,0 +1,44 @@ +name: Obfuscate Main Build + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: write + +jobs: + obfuscate: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Run release obfuscation + run: python scripts/obfuscate_release.py --root . --hash-assets + + - name: Commit obfuscated build + run: | + git add -A + if git diff --cached --quiet; then + echo "No build changes to commit." + exit 0 + fi + git commit -m "chore(release): obfuscate and hash production assets [skip ci]" + git push diff --git a/README.md b/README.md index 0366fd7..f9fbb03 100644 --- a/README.md +++ b/README.md @@ -166,25 +166,22 @@ Für den Produktivbetrieb `public/` als Webroot konfigurieren. ### Production-Build & Veröffentlichung -Der Quellcode bleibt auf `dev`, der veröffentlichte Stand liegt auf `main` (ohne Kommentare, obfuskiertes JS). +Der Quellcode bleibt auf `dev`, der veröffentlichte Stand liegt auf `main`. -**Voraussetzungen:** Node.js 18+ (inkl. npm), PHP 8+ CLI, Git +Bei jedem Push/Merge auf `main` läuft die GitHub Action `.github/workflows/obfuscate-main.yml` automatisch und führt aus: -```powershell -# Windows -.\scripts\run-build.ps1 -.\scripts\publish-to-main.ps1 -Push -``` +- Entfernen von Kommentaren (inkl. Block-Kommentaren) in PHP/JS/CSS +- Minify + Obfuscate für JavaScript +- Minify für CSS +- Kein Source-Map-Output +- Hashing von JS/CSS-Dateinamen + automatische Referenz-Anpassung + +Lokal ausführbar: ```bash -# Linux / macOS -chmod +x scripts/*.sh -./scripts/run-build.sh -./scripts/publish-to-main.sh --push +python scripts/obfuscate_release.py --root . --hash-assets ``` -Details: `scripts/build/README.md` - ## 🔗 Backend-Integration Das Backend-Repository enthält folgende wiederverwendbare Komponenten: diff --git a/scripts/__pycache__/obfuscate_release.cpython-314.pyc b/scripts/__pycache__/obfuscate_release.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..475c68f4eac09741fed4f2a823e0f1a7aa39348a GIT binary patch literal 13123 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 str: + out = [] + i = 0 + n = len(text) + in_single = False + in_double = False + in_template = False + escape = False + in_line_comment = False + in_block_comment = False + + while i < n: + ch = text[i] + nxt = text[i + 1] if i + 1 < n else "" + + if in_line_comment: + if ch == "\n": + in_line_comment = False + out.append(ch) + i += 1 + continue + + if in_block_comment: + if ch == "*" and nxt == "/": + in_block_comment = False + i += 2 + else: + if ch == "\n": + out.append("\n") + i += 1 + continue + + if in_single or in_double or in_template: + out.append(ch) + if escape: + escape = False + elif ch == "\\": + escape = True + elif in_single and ch == "'": + in_single = False + elif in_double and ch == '"': + in_double = False + elif in_template and ch == "`": + in_template = False + i += 1 + continue + + if ch == "/" and nxt == "/": + in_line_comment = True + i += 2 + continue + if ch == "/" and nxt == "*": + in_block_comment = True + i += 2 + continue + + if ch == "'": + in_single = True + out.append(ch) + i += 1 + continue + if ch == '"': + in_double = True + out.append(ch) + i += 1 + continue + if ch == "`": + in_template = True + out.append(ch) + i += 1 + continue + + out.append(ch) + i += 1 + + return "".join(out) + + +def strip_php_comments(text: str) -> str: + out = [] + i = 0 + n = len(text) + in_single = False + in_double = False + in_line_comment = False + in_block_comment = False + escape = False + + while i < n: + ch = text[i] + nxt = text[i + 1] if i + 1 < n else "" + + if in_line_comment: + if ch == "\n": + in_line_comment = False + out.append("\n") + i += 1 + continue + + if in_block_comment: + if ch == "*" and nxt == "/": + in_block_comment = False + i += 2 + else: + if ch == "\n": + out.append("\n") + i += 1 + continue + + if in_single or in_double: + out.append(ch) + if escape: + escape = False + elif ch == "\\": + escape = True + elif in_single and ch == "'": + in_single = False + elif in_double and ch == '"': + in_double = False + i += 1 + continue + + if ch == "/" and nxt == "/": + in_line_comment = True + i += 2 + continue + if ch == "#": + in_line_comment = True + i += 1 + continue + if ch == "/" and nxt == "*": + in_block_comment = True + i += 2 + continue + + if ch == "'": + in_single = True + elif ch == '"': + in_double = True + + out.append(ch) + i += 1 + + return "".join(out) + + +def minify_css_fallback(text: str) -> str: + text = strip_comments_keep_strings(text) + text = re.sub(r"\s+", " ", text) + text = re.sub(r"\s*([{}:;,>+~])\s*", r"\1", text) + return text.strip() + + +def minify_js_fallback(text: str) -> str: + text = strip_comments_keep_strings(text) + text = re.sub(r"\s+", " ", text) + text = re.sub(r"\s*([{}:;,()=+\-*/<>!&|?])\s*", r"\1", text) + return text.strip() + + +def run_cmd(command: list[str], cwd: Path, input_text: str | None = None) -> str: + proc = subprocess.run( + command, + cwd=str(cwd), + input=input_text, + text=True, + capture_output=True, + check=False, + ) + if proc.returncode != 0: + raise RuntimeError(proc.stderr.strip() or "command failed") + return proc.stdout + + +def process_js(path: Path, cwd: Path) -> None: + original = path.read_text(encoding="utf-8") + if shutil.which("npx"): + try: + minified = run_cmd( + [ + "npx", + "--yes", + "terser", + "--compress", + "--mangle", + "--comments", + "false", + ], + cwd, + input_text=original, + ) + obfuscated = run_cmd( + [ + "npx", + "--yes", + "javascript-obfuscator", + "--compact", + "true", + "--control-flow-flattening", + "true", + "--dead-code-injection", + "true", + "--string-array", + "true", + "--string-array-encoding", + "base64", + "--target", + "browser-no-eval", + "--source-map", + "false", + "--output", + "stdout", + ], + cwd, + input_text=minified, + ) + path.write_text(obfuscated.strip() + "\n", encoding="utf-8") + return + except Exception: + pass + path.write_text(minify_js_fallback(original) + "\n", encoding="utf-8") + + +def process_css(path: Path, cwd: Path) -> None: + original = path.read_text(encoding="utf-8") + if shutil.which("npx"): + try: + minified = run_cmd( + [ + "npx", + "--yes", + "clean-css-cli", + "--skip-rebase", + "-O2", + ], + cwd, + input_text=original, + ) + path.write_text(minified.strip() + "\n", encoding="utf-8") + return + except Exception: + pass + path.write_text(minify_css_fallback(original) + "\n", encoding="utf-8") + + +def process_php(path: Path) -> None: + original = path.read_text(encoding="utf-8") + stripped = strip_php_comments(original) + path.write_text(stripped, encoding="utf-8") + + +def hash_file(path: Path) -> str: + digest = hashlib.sha256(path.read_bytes()).hexdigest()[:12] + return digest + + +def replace_references(root: Path, mapping: dict[str, str]) -> None: + for candidate in root.rglob("*"): + if not candidate.is_file() or candidate.suffix.lower() not in TEXT_EXTENSIONS: + continue + try: + content = candidate.read_text(encoding="utf-8") + except UnicodeDecodeError: + continue + updated = content + for src, dst in mapping.items(): + updated = updated.replace(src, dst) + updated = updated.replace("/" + src, "/" + dst) + if updated != content: + candidate.write_text(updated, encoding="utf-8") + + +def build_hash_mapping(public_root: Path) -> dict[str, str]: + mapping: dict[str, str] = {} + asset_root = public_root / "assets" + for ext in (".js", ".css"): + for file_path in sorted(asset_root.rglob(f"*{ext}")): + if ".min." in file_path.name or ".obf." in file_path.name: + continue + digest = hash_file(file_path) + new_name = f"{file_path.stem}.{digest}{file_path.suffix}" + new_path = file_path.with_name(new_name) + file_path.rename(new_path) + rel_old = file_path.relative_to(public_root).as_posix() + rel_new = new_path.relative_to(public_root).as_posix() + mapping[rel_old] = rel_new + return mapping + + +def main() -> int: + parser = argparse.ArgumentParser(description="Release obfuscation build.") + parser.add_argument("--root", default=".", help="Repository root") + parser.add_argument("--hash-assets", action="store_true", help="Hash JS/CSS file names") + args = parser.parse_args() + + repo_root = Path(args.root).resolve() + public_root = repo_root / "public" + + if not public_root.exists(): + print("public directory not found", file=sys.stderr) + return 1 + + for js in sorted(public_root.rglob("*.js")): + process_js(js, repo_root) + for css in sorted(public_root.rglob("*.css")): + process_css(css, repo_root) + for php in sorted((repo_root / "public").rglob("*.php")): + process_php(php) + for php in sorted((repo_root / "backend").rglob("*.php")): + process_php(php) + + if args.hash_assets: + mapping = build_hash_mapping(public_root) + replace_references(repo_root, mapping) + + print("Release obfuscation complete.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/publish-to-main.ps1 b/scripts/publish-to-main.ps1 deleted file mode 100644 index 2a472eb..0000000 --- a/scripts/publish-to-main.ps1 +++ /dev/null @@ -1,185 +0,0 @@ -#Requires -Version 5.1 -<# -.SYNOPSIS - Erstellt einen Production-Build und veroeffentlicht ihn auf den Branch main. - -.DESCRIPTION - 1. Wechselt auf main und setzt ihn auf den Stand von dev - 2. Entfernt Kommentare, minifiziert CSS, obfuskiert JavaScript - 3. Committet und pusht main (optional) - 4. Wechselt zurueck auf dev (Quellcode bleibt unveraendert) - -.PARAMETER Push - Pusht main nach origin (Standard: nur lokaler Commit) - -.PARAMETER DryRun - Fuehrt Git-Schritte nur simuliert aus (Build wird trotzdem erstellt) - -.PARAMETER AllowDirty - Erlaubt uncommittete Aenderungen - -.PARAMETER Message - Commit-Nachricht fuer den Production-Build - -.EXAMPLE - .\scripts\publish-to-main.ps1 - -.EXAMPLE - .\scripts\publish-to-main.ps1 -Push -#> -[CmdletBinding()] -param( - [switch]$Push, - [switch]$DryRun, - [switch]$AllowDirty, - [string]$Message = "" -) - -$ErrorActionPreference = "Stop" -$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path -$BuildDir = Join-Path $Root "scripts\build" -$OriginalBranch = "" - -function Write-Step([string]$Text) { - Write-Host "" - Write-Host "==> $Text" -ForegroundColor Cyan -} - -function Ensure-GitClean { - $status = git -C $Root status --porcelain - if ($status) { - throw "Uncommittete Aenderungen im Repository. Bitte zuerst committen oder stashen." - } -} - -function Resolve-NodeTool([string]$ToolName) { - $command = Get-Command $ToolName -ErrorAction SilentlyContinue - if ($command) { - return $command.Source - } - - $candidates = @( - (Join-Path $env:ProgramFiles "nodejs\$ToolName.cmd"), - (Join-Path ${env:ProgramFiles(x86)} "nodejs\$ToolName.cmd"), - (Join-Path $env:LOCALAPPDATA "Programs\nodejs\$ToolName.cmd"), - "c:\Program Files\cursor\resources\app\resources\helpers\node.exe" - ) - - foreach ($candidate in $candidates) { - if ($ToolName -eq "node" -and (Test-Path $candidate)) { - return $candidate - } - if ($ToolName -ne "node" -and (Test-Path $candidate)) { - return $candidate - } - } - - return $null -} - -function Ensure-Node { - $script:NodeExe = Resolve-NodeTool "node" - $script:NpmExe = Resolve-NodeTool "npm" - - if (-not $script:NodeExe) { - throw "Node.js ist nicht installiert. Bitte Node.js 18+ installieren: https://nodejs.org/" - } - if (-not $script:NpmExe) { - throw "npm wurde nicht gefunden. Bitte Node.js inkl. npm installieren und PATH setzen." - } -} - -try { - Set-Location $Root - Ensure-Node - if (-not $AllowDirty) { - Ensure-GitClean - } else { - Write-Warning "AllowDirty aktiv - uncommittete Aenderungen werden mit veroeffentlicht." - } - - $OriginalBranch = (git branch --show-current).Trim() - if ($OriginalBranch -ne "dev") { - Write-Warning "Empfohlen: Auf Branch 'dev' starten (aktuell: $OriginalBranch)" - } - - if ([string]::IsNullOrWhiteSpace($Message)) { - $Message = "chore(release): production build $(Get-Date -Format 'yyyy-MM-dd HH:mm')" - } - - Write-Step "Installiere Build-Abhaengigkeiten" - Set-Location $BuildDir - if (-not $DryRun) { - & $NpmExe ci --no-fund --no-audit - if ($LASTEXITCODE -ne 0) { throw "npm ci fehlgeschlagen" } - } - - Write-Step "Wechsle auf main und synchronisiere mit dev" - Set-Location $Root - if ($DryRun) { - Write-Host '[DryRun] git checkout main' - Write-Host '[DryRun] git reset --hard dev' - } else { - git checkout main - git reset --hard dev - } - - Write-Step "Production-Build (Kommentare entfernen, JS obfuscaten)" - Set-Location $BuildDir - if ($DryRun) { - Write-Host '[DryRun] npm run build:in-place' - } else { - & $NpmExe run build:in-place - if ($LASTEXITCODE -ne 0) { throw "Production-Build fehlgeschlagen" } - } - - Write-Step "Production-Build committen" - Set-Location $Root - if ($DryRun) { - Write-Host '[DryRun] git add -A' - Write-Host ('[DryRun] git commit -m "' + $Message + '"') - } else { - git add -A - $null = git diff --cached --quiet - if ($LASTEXITCODE -eq 0) { - Write-Warning "Keine Build-Aenderungen - nichts zu committen." - } else { - git commit -m $Message - } - } - - if ($Push) { - Write-Step "Push nach origin/main" - if ($DryRun) { - Write-Host '[DryRun] git push origin main' - } else { - git push origin main - } - } else { - Write-Host "Hinweis: Ohne -Push wurde nur lokal auf main gebaut." -ForegroundColor Yellow - } - - Write-Step "Zurueck auf $OriginalBranch" - if (-not $DryRun) { - if ([string]::IsNullOrWhiteSpace($OriginalBranch)) { - git checkout dev - } else { - git checkout $OriginalBranch - } - } - - Write-Host "" - Write-Host "Production-Release abgeschlossen." -ForegroundColor Green - if (-not $Push -and -not $DryRun) { - Write-Host "Zum Veroeffentlichen: git push origin main" -ForegroundColor Yellow - } -} -catch { - Write-Host "" - Write-Host ('FEHLER: ' + $_.Exception.Message) -ForegroundColor Red - Set-Location $Root - if ($OriginalBranch -and -not $DryRun) { - git checkout $OriginalBranch 2>$null - } - exit 1 -} diff --git a/scripts/publish-to-main.sh b/scripts/publish-to-main.sh deleted file mode 100644 index f2f4aaa..0000000 --- a/scripts/publish-to-main.sh +++ /dev/null @@ -1,187 +0,0 @@ -#!/usr/bin/env bash -# -# Erstellt einen Production-Build und veröffentlicht ihn auf den Branch main. -# -# 1. Wechselt auf main und setzt ihn auf den Stand von dev -# 2. Entfernt Kommentare, minifiziert CSS, obfuskiert JavaScript -# 3. Committet und pusht main (optional) -# 4. Wechselt zurück auf den ursprünglichen Branch (dev bleibt unverändert) -# -# Nutzung: -# ./scripts/publish-to-main.sh -# ./scripts/publish-to-main.sh --push -# ./scripts/publish-to-main.sh --dry-run -# ./scripts/publish-to-main.sh --allow-dirty --message "chore(release): v1.2" -# - -set -euo pipefail - -PUSH=false -DRY_RUN=false -ALLOW_DIRTY=false -MESSAGE="" - -usage() { - cat <<'EOF' -Usage: publish-to-main.sh [OPTIONS] - -Options: - --push Push nach origin/main - --dry-run Git-Schritte nur anzeigen (Build wird ausgeführt) - --allow-dirty Uncommittete Änderungen erlauben - --message TEXT Commit-Nachricht - -h, --help Hilfe anzeigen -EOF -} - -while [[ $# -gt 0 ]]; do - case "$1" in - --push) - PUSH=true - shift - ;; - --dry-run) - DRY_RUN=true - shift - ;; - --allow-dirty) - ALLOW_DIRTY=true - shift - ;; - --message) - MESSAGE="${2:-}" - shift 2 - ;; - -h|--help) - usage - exit 0 - ;; - *) - echo "Unbekannte Option: $1" >&2 - usage >&2 - exit 1 - ;; - esac -done - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -BUILD_DIR="$ROOT/scripts/build" -ORIGINAL_BRANCH="" - -step() { - echo "" - echo "==> $1" -} - -require_command() { - if ! command -v "$1" >/dev/null 2>&1; then - echo "FEHLER: '$1' nicht gefunden. Bitte installieren und PATH setzen." >&2 - exit 1 - fi -} - -ensure_git_clean() { - if [[ -n "$(git -C "$ROOT" status --porcelain)" ]]; then - echo "FEHLER: Uncommittete Änderungen. Bitte zuerst committen oder stashen." >&2 - exit 1 - fi -} - -cleanup_on_error() { - echo "" - echo "FEHLER: Abgebrochen." >&2 - cd "$ROOT" || true - if [[ -n "$ORIGINAL_BRANCH" && "$DRY_RUN" == false ]]; then - git checkout "$ORIGINAL_BRANCH" 2>/dev/null || true - fi -} - -trap cleanup_on_error ERR - -require_command node -require_command npm -require_command git - -cd "$ROOT" - -if [[ "$ALLOW_DIRTY" == false ]]; then - ensure_git_clean -else - echo "WARNUNG: --allow-dirty aktiv – uncommittete Änderungen werden mit veröffentlicht." >&2 -fi - -ORIGINAL_BRANCH="$(git branch --show-current | tr -d '[:space:]')" -if [[ "$ORIGINAL_BRANCH" != "dev" ]]; then - echo "WARNUNG: Empfohlen auf Branch 'dev' zu starten (aktuell: ${ORIGINAL_BRANCH:-detached})" >&2 -fi - -if [[ -z "$MESSAGE" ]]; then - MESSAGE="chore(release): production build $(date '+%Y-%m-%d %H:%M')" -fi - -step "Installiere Build-Abhängigkeiten" -cd "$BUILD_DIR" -if [[ "$DRY_RUN" == false ]]; then - npm ci --no-fund --no-audit -fi - -step "Wechsle auf main und synchronisiere mit dev" -cd "$ROOT" -if [[ "$DRY_RUN" == true ]]; then - echo "[DryRun] git checkout main" - echo "[DryRun] git reset --hard dev" -else - git checkout main - git reset --hard dev -fi - -step "Production-Build (Kommentare entfernen, JS obfuscaten)" -cd "$BUILD_DIR" -if [[ "$DRY_RUN" == true ]]; then - echo "[DryRun] npm run build:in-place" -else - npm run build:in-place -fi - -step "Production-Build committen" -cd "$ROOT" -if [[ "$DRY_RUN" == true ]]; then - echo "[DryRun] git add -A" - echo "[DryRun] git commit -m \"$MESSAGE\"" -else - git add -A - if git diff --cached --quiet; then - echo "WARNUNG: Keine Build-Änderungen – nichts zu committen." >&2 - else - git commit -m "$MESSAGE" - fi -fi - -if [[ "$PUSH" == true ]]; then - step "Push nach origin/main" - if [[ "$DRY_RUN" == true ]]; then - echo "[DryRun] git push origin main" - else - git push origin main - fi -else - echo "Hinweis: Ohne --push wurde nur lokal auf main gebaut." -fi - -step "Zurück auf ${ORIGINAL_BRANCH:-dev}" -if [[ "$DRY_RUN" == false ]]; then - if [[ -n "$ORIGINAL_BRANCH" ]]; then - git checkout "$ORIGINAL_BRANCH" - else - git checkout dev - fi -fi - -trap - ERR - -echo "" -echo "Production-Release abgeschlossen." -if [[ "$PUSH" == false && "$DRY_RUN" == false ]]; then - echo "Zum Veröffentlichen: git push origin main" -fi diff --git a/scripts/run-build.ps1 b/scripts/run-build.ps1 deleted file mode 100644 index b60b860..0000000 --- a/scripts/run-build.ps1 +++ /dev/null @@ -1,46 +0,0 @@ -#Requires -Version 5.1 -<# -.SYNOPSIS - Erstellt ein Production-Bundle unter dist/ (ohne Branch-Wechsel). -#> -[CmdletBinding()] -param( - [switch]$InPlace -) - -$ErrorActionPreference = "Stop" -$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path -$BuildDir = Join-Path $Root "scripts\build" - -function Resolve-NodeTool([string]$ToolName) { - $command = Get-Command $ToolName -ErrorAction SilentlyContinue - if ($command) { return $command.Source } - - $candidates = @( - (Join-Path $env:ProgramFiles "nodejs\$ToolName.cmd"), - (Join-Path ${env:ProgramFiles(x86)} "nodejs\$ToolName.cmd") - ) - - foreach ($candidate in $candidates) { - if (Test-Path $candidate) { return $candidate } - } - - return $null -} - -$npm = Resolve-NodeTool "npm" -if (-not $npm) { - throw "npm nicht gefunden. Bitte Node.js installieren." -} - -Set-Location $BuildDir -& $npm ci --no-fund --no-audit -if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - -if ($InPlace) { - & $npm run build:in-place -} else { - & $npm run build -} - -exit $LASTEXITCODE diff --git a/scripts/run-build.sh b/scripts/run-build.sh deleted file mode 100644 index 7009c73..0000000 --- a/scripts/run-build.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env bash -# -# Erstellt ein Production-Bundle unter dist/ (ohne Branch-Wechsel). -# -# Nutzung: -# ./scripts/run-build.sh -# ./scripts/run-build.sh --in-place -# - -set -euo pipefail - -IN_PLACE=false - -usage() { - cat <<'EOF' -Usage: run-build.sh [OPTIONS] - -Options: - --in-place Build direkt im Repository (statt dist/) - -h, --help Hilfe anzeigen -EOF -} - -while [[ $# -gt 0 ]]; do - case "$1" in - --in-place) - IN_PLACE=true - shift - ;; - -h|--help) - usage - exit 0 - ;; - *) - echo "Unbekannte Option: $1" >&2 - usage >&2 - exit 1 - ;; - esac -done - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -BUILD_DIR="$ROOT/scripts/build" - -if ! command -v npm >/dev/null 2>&1; then - echo "FEHLER: npm nicht gefunden. Bitte Node.js installieren." >&2 - exit 1 -fi - -cd "$BUILD_DIR" -npm ci --no-fund --no-audit - -if [[ "$IN_PLACE" == true ]]; then - npm run build:in-place -else - npm run build -fi