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.
This commit is contained in:
44
.github/workflows/obfuscate-main.yml
vendored
Normal file
44
.github/workflows/obfuscate-main.yml
vendored
Normal file
@@ -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
|
||||
23
README.md
23
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:
|
||||
|
||||
BIN
scripts/__pycache__/obfuscate_release.cpython-314.pyc
Normal file
BIN
scripts/__pycache__/obfuscate_release.cpython-314.pyc
Normal file
Binary file not shown.
337
scripts/obfuscate_release.py
Normal file
337
scripts/obfuscate_release.py
Normal file
@@ -0,0 +1,337 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
TEXT_EXTENSIONS = {".php", ".html", ".htm", ".xml", ".txt", ".js", ".css"}
|
||||
|
||||
|
||||
def strip_comments_keep_strings(text: str) -> 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())
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user