Build pipeline hardening: ErrorBoundary, no PHP copies, TS pre-flight

- ErrorBoundary.tsx wraps widget — crashes show fallback, not blank void
- build.sh v1.1.0: removed ALL PHP file copies (Chronicler deploys manually)
- Added set -e / set -u for fail-fast
- Added TypeScript pre-flight check (yarn tsc --noEmit) before build
- Added dynamic Blueprint controller detection via find
- Widget injection now wrapped in <ModpackErrorBoundary>
- Pre-commit PHP lint hook at scripts/pre-commit-hook.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude (Chronicler #83 - The Compiler)
2026-04-13 00:17:30 -05:00
parent 7b2f3f810b
commit 28608e9fa8
6 changed files with 267 additions and 56 deletions

View File

@@ -3,19 +3,21 @@
# MODPACK VERSION CHECKER - BUILD SCRIPT
# =============================================================================
#
# Executes automatically during `blueprint -build`
# Injects React components into Pterodactyl's frontend
# Handles TSX injection + frontend compilation ONLY.
# PHP files are deployed manually by Chronicler from the repo.
#
# @author Firefrost Gaming / Frostystyle <dev@firefrostgaming.com>
# @version 1.0.0
# @version 1.1.0
# =============================================================================
set -e
set -u
echo "=========================================="
echo "ModpackChecker Build Script v1.0.0"
echo "ModpackChecker Build Script v1.1.0"
echo "=========================================="
# Determine the extension source directory
# Blueprint may run from .blueprint/dev/ or .blueprint/extensions/modpackchecker/
if [ -d ".blueprint/extensions/modpackchecker/views" ]; then
EXT_DIR=".blueprint/extensions/modpackchecker"
elif [ -d ".blueprint/dev/views" ]; then
@@ -35,7 +37,6 @@ echo "Detected Node.js version: v$NODE_MAJOR_VERSION"
if [ "$NODE_MAJOR_VERSION" -lt 16 ]; then
echo "ERROR: ModpackChecker requires Node.js 16 or higher."
echo "Please upgrade Node.js on your panel and try again."
exit 1
fi
@@ -45,7 +46,7 @@ if [ "$NODE_MAJOR_VERSION" -ge 17 ]; then
fi
# ===========================================
# 1. CONSOLE WIDGET INJECTION (Right Column)
# 1. CONSOLE WIDGET + ERROR BOUNDARY
# ===========================================
echo ""
echo "--- Console Widget ---"
@@ -57,15 +58,20 @@ else
echo "⚠ wrapper.tsx not found, skipping console widget"
fi
# Inject into AfterInformation.tsx (right column, after stats)
if [ -f "$EXT_DIR/views/server/ErrorBoundary.tsx" ]; then
cp "$EXT_DIR/views/server/ErrorBoundary.tsx" resources/scripts/components/server/ModpackErrorBoundary.tsx
echo "✓ Copied ModpackErrorBoundary.tsx"
else
echo "⚠ ErrorBoundary.tsx not found, skipping error boundary"
fi
# Inject into AfterInformation.tsx (wrapped in ErrorBoundary)
AFTER_INFO="resources/scripts/blueprint/components/Server/Terminal/AfterInformation.tsx"
if [ -f "$AFTER_INFO" ]; then
if ! grep -q "ModpackVersionCard" "$AFTER_INFO" 2>/dev/null; then
# Add import after the blueprint/import comment
sed -i '/\/\* blueprint\/import \*\//a import ModpackVersionCard from "@/components/server/ModpackVersionCard";' "$AFTER_INFO"
# Add component inside the fragment after blueprint/react comment
sed -i 's|{/\* blueprint/react \*/}|{/* blueprint/react */}\n <ModpackVersionCard />|' "$AFTER_INFO"
echo "✓ Injected ModpackVersionCard into AfterInformation.tsx"
sed -i '/\/\* blueprint\/import \*\//a import ModpackVersionCard from "@/components/server/ModpackVersionCard";\nimport ModpackErrorBoundary from "@/components/server/ModpackErrorBoundary";' "$AFTER_INFO"
sed -i 's|{/\* blueprint/react \*/}|{/* blueprint/react */}\n <ModpackErrorBoundary><ModpackVersionCard /></ModpackErrorBoundary>|' "$AFTER_INFO"
echo "✓ Injected ModpackVersionCard (with ErrorBoundary) into AfterInformation.tsx"
else
echo "○ ModpackVersionCard already present in AfterInformation.tsx"
fi
@@ -87,10 +93,8 @@ else
echo "⚠ UpdateBadge.tsx not found, skipping dashboard badge"
fi
# Inject into ServerRow.tsx (dashboard server list)
if ! grep -q "UpdateBadge" resources/scripts/components/dashboard/ServerRow.tsx 2>/dev/null; then
sed -i '1i import UpdateBadge from "@/components/dashboard/UpdateBadge";' resources/scripts/components/dashboard/ServerRow.tsx
# Targeted replacement: append badge after server name
sed -i 's|{server.name}</p>|{server.name}<UpdateBadge serverUuid={server.uuid} /></p>|' resources/scripts/components/dashboard/ServerRow.tsx
echo "✓ Injected UpdateBadge into ServerRow.tsx"
else
@@ -98,56 +102,39 @@ else
fi
# ===========================================
# 3. PHP SERVICE + COMMAND INJECTION
# 3. ADMIN CONTROLLER (Blueprint auto-generated)
# ===========================================
# Blueprint's requests.app merge doesn't reliably create new subdirectories.
# Explicitly copy PHP classes to the main Laravel app/ tree.
echo ""
echo "--- PHP Classes ---"
mkdir -p app/Services
cp "$EXT_DIR/app/Services/LicenseService.php" app/Services/LicenseService.php
echo "✓ Copied LicenseService.php"
cp "$EXT_DIR/app/Services/ModpackApiService.php" app/Services/ModpackApiService.php
echo "✓ Copied ModpackApiService.php"
mkdir -p app/Console/Commands
cp "$EXT_DIR/app/Console/Commands/ValidateLicense.php" app/Console/Commands/ValidateLicense.php
echo "✓ Copied ValidateLicense.php"
cp "$EXT_DIR/app/Console/Commands/CheckModpackUpdates.php" app/Console/Commands/CheckModpackUpdates.php
echo "✓ Copied CheckModpackUpdates.php"
mkdir -p app/Http/Controllers
cp "$EXT_DIR/app/Http/Controllers/ModpackAPIController.php" app/Http/Controllers/ModpackAPIController.php
echo "✓ Copied ModpackAPIController.php"
# ===========================================
# 4. OVERWRITE BLUEPRINT'S AUTO-GENERATED CONTROLLER
# ===========================================
# Blueprint generates its own controller wrapper at this path.
# Our admin/controller.php has LicenseService DI — overwrite the generated one.
echo ""
echo "--- Admin Controller ---"
CTRL_DIR="app/Http/Controllers/Admin/Extensions/modpackchecker"
if [ -d "$CTRL_DIR" ]; then
cp "$EXT_DIR/admin/controller.php" "$CTRL_DIR/modpackcheckerExtensionController.php"
echo "✓ Overwrote Blueprint controller with licensed version"
BP_CONTROLLER=$(find app/Http/Controllers/Admin/Extensions/modpackchecker -name "*Controller.php" 2>/dev/null | head -n 1)
if [ -n "$BP_CONTROLLER" ]; then
cp "$EXT_DIR/admin/controller.php" "$BP_CONTROLLER"
echo "✓ Overwrote Blueprint controller: $BP_CONTROLLER"
else
mkdir -p "$CTRL_DIR"
cp "$EXT_DIR/admin/controller.php" "$CTRL_DIR/modpackcheckerExtensionController.php"
echo "✓ Created admin controller with license support"
echo "⚠ Blueprint controller not found — admin panel may need manual controller deploy"
fi
# ===========================================
# 5. CLEAR CACHES
# 4. CLEAR CACHES
# ===========================================
echo ""
echo "--- Cache Clear ---"
php artisan optimize:clear 2>/dev/null && echo "✓ Laravel caches cleared" || echo "⚠ Cache clear skipped (non-fatal)"
# ===========================================
# 5. TYPESCRIPT PRE-FLIGHT CHECK
# ===========================================
echo ""
echo "--- TypeScript Check ---"
if command -v yarn &>/dev/null; then
yarn tsc --noEmit 2>&1 | head -20 || {
echo "❌ TypeScript validation failed — aborting build"
exit 1
}
echo "✓ TypeScript check passed"
fi
# ===========================================
# 6. COMPILE FRONTEND ASSETS
# ===========================================
@@ -155,9 +142,12 @@ echo ""
echo "--- Frontend Build ---"
if command -v yarn &>/dev/null; then
echo "Running yarn build:production (this may take 2-5 minutes)..."
yarn build:production 2>&1 && echo "✓ Frontend assets compiled" || echo "⚠ Frontend build failed — dashboard badges will not render, but admin + console features work fine"
yarn build:production 2>&1 && echo "✓ Frontend assets compiled" || {
echo "⚠ Frontend build failed — dashboard badges and widget will not render"
echo " Admin panel + cron + license system still work fine"
}
else
echo "⚠ yarn not found — skipping frontend build. Dashboard badges require manual yarn build:production"
echo "⚠ yarn not found — skipping frontend build"
fi
echo ""
@@ -166,6 +156,7 @@ echo "ModpackChecker build complete!"
echo "=========================================="
echo ""
echo "Next steps:"
echo " 1. Restart: systemctl restart php8.3-fpm"
echo " 2. Test cron: php artisan modpackchecker:check"
echo " 1. Deploy PHP files from repo (Chronicler)"
echo " 2. Restart: systemctl restart php8.3-fpm"
echo " 3. Test cron: php artisan modpackchecker:check"
echo ""

View File

@@ -0,0 +1,27 @@
import React, { Component, ErrorInfo, ReactNode } from "react";
interface Props { children?: ReactNode; fallback?: ReactNode; }
interface State { hasError: boolean; }
class ModpackErrorBoundary extends Component<Props, State> {
public state: State = { hasError: false };
public static getDerivedStateFromError(_: Error): State {
return { hasError: true };
}
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error("ModpackChecker Component Error:", error, errorInfo);
}
public render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="text-gray-400 text-xs px-2 py-1">Modpack module unavailable.</div>
);
}
return this.props.children;
}
}
export default ModpackErrorBoundary;