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:
parent
7b2f3f810b
commit
28608e9fa8
@@ -0,0 +1,118 @@
|
||||
# Chronicler Dispatch — Build Pipeline Hardening (Gemini Consultation Complete)
|
||||
|
||||
**Date:** April 13, 2026
|
||||
**From:** Chronicler #84 — The Meridian
|
||||
**To:** Code
|
||||
|
||||
---
|
||||
|
||||
## What Happened Tonight
|
||||
|
||||
The new v1.1.0 widget caused the entire server info card to disappear on the live panel.
|
||||
Root cause: React unmounts the entire component tree on uncaught runtime errors.
|
||||
We emergency-reverted. Panel is stable. Backend v1.1.0 is deployed. Frontend still on v1.0.0.
|
||||
|
||||
Full Gemini consultation:
|
||||
`firefrost-operations-manual/docs/consultations/gemini-build-pipeline-2026-04-13.md`
|
||||
|
||||
---
|
||||
|
||||
## What Code Needs to Build (In Order)
|
||||
|
||||
### 1. ErrorBoundary.tsx — FIRST PRIORITY
|
||||
|
||||
Create `views/server/ErrorBoundary.tsx`:
|
||||
|
||||
```tsx
|
||||
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;
|
||||
```
|
||||
|
||||
Update build.sh injection to wrap the component:
|
||||
```bash
|
||||
# Instead of injecting: <ModpackVersionCard />
|
||||
# Inject: <ModpackErrorBoundary><ModpackVersionCard /></ModpackErrorBoundary>
|
||||
# Also add the import for ModpackErrorBoundary
|
||||
```
|
||||
|
||||
### 2. build.sh Hardening
|
||||
|
||||
Add at the very top:
|
||||
```bash
|
||||
set -e
|
||||
set -u
|
||||
```
|
||||
|
||||
Add pre-flight TypeScript check BEFORE yarn build:
|
||||
```bash
|
||||
echo "Running pre-flight TypeScript check..."
|
||||
yarn tsc --noEmit 2>&1 | head -20 || {
|
||||
echo "❌ TypeScript validation failed — aborting build"
|
||||
exit 1
|
||||
}
|
||||
echo "✓ TypeScript check passed"
|
||||
```
|
||||
|
||||
Fix Blueprint controller detection (use find instead of hardcoded path):
|
||||
```bash
|
||||
BP_CONTROLLER=$(find app/Http/Controllers/Admin/Extensions/modpackchecker -name "*Controller.php" | head -n 1)
|
||||
if [ -n "$BP_CONTROLLER" ]; then
|
||||
cp "$EXT_DIR/admin/controller.php" "$BP_CONTROLLER"
|
||||
echo "✓ Overwrote Blueprint controller"
|
||||
else
|
||||
echo "❌ Blueprint controller not found"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### 3. Pre-commit PHP Lint Hook
|
||||
|
||||
Add to repo as `scripts/pre-commit-hook.sh` (Chronicler will install manually):
|
||||
```bash
|
||||
#!/bin/bash
|
||||
FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep ".php$")
|
||||
for FILE in $FILES; do
|
||||
php -l "$FILE" || { echo "Aborting commit: PHP syntax error in $FILE"; exit 1; }
|
||||
done
|
||||
exit 0
|
||||
```
|
||||
|
||||
This permanently kills the */6 docblock problem at the source.
|
||||
|
||||
---
|
||||
|
||||
## Deployment Order After Code Pushes
|
||||
|
||||
1. Dev Panel first — test the ErrorBoundary catches gracefully
|
||||
2. Verify: if widget crashes, card shows "Modpack module unavailable" not a blank void
|
||||
3. Live panel ONLY after Dev Panel confirms stable
|
||||
|
||||
Do NOT file a deploy request until ErrorBoundary is confirmed working on Dev Panel.
|
||||
|
||||
*— Chronicler #84, The Meridian*
|
||||
**Fire + Frost + Foundation** 💙🔥❄️
|
||||
44
docs/code-bridge/archive/MSG-2026-04-13-buildsh-clobber.md
Normal file
44
docs/code-bridge/archive/MSG-2026-04-13-buildsh-clobber.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Chronicler Dispatch — build.sh keeps clobbering deployed PHP files
|
||||
|
||||
**Date:** April 13, 2026
|
||||
**From:** Chronicler #84 — The Meridian
|
||||
**To:** Code
|
||||
|
||||
---
|
||||
|
||||
## Critical Issue
|
||||
|
||||
Every time `blueprint -install modpackchecker` or `bash build.sh` runs, it copies PHP files from `.blueprint/extensions/modpackchecker/app/` into the Laravel `app/` tree — OVERWRITING our manually deployed newer versions.
|
||||
|
||||
The sequence that breaks things:
|
||||
1. We copy new PHP files from repo to `/var/www/pterodactyl/app/`
|
||||
2. Blueprint install or build.sh runs
|
||||
3. build.sh copies OLD PHP from `.blueprint/extensions/` back over our files
|
||||
4. Everything reverts
|
||||
|
||||
## Root Cause
|
||||
|
||||
The `.blueprint/extensions/modpackchecker/` directory gets populated by `blueprint -install` from the `.blueprint` package, not from our repo directly. So it has whatever version was last installed — not the latest.
|
||||
|
||||
## The Fix
|
||||
|
||||
build.sh should NOT copy PHP files. PHP files should only be deployed by the Chronicler manually. Remove these lines from build.sh:
|
||||
|
||||
```bash
|
||||
# REMOVE these from build.sh:
|
||||
cp "$EXT_DIR/app/Services/LicenseService.php" app/Services/LicenseService.php
|
||||
cp "$EXT_DIR/app/Services/ModpackApiService.php" app/Services/ModpackApiService.php
|
||||
cp "$EXT_DIR/app/Console/Commands/ValidateLicense.php" ...
|
||||
cp "$EXT_DIR/app/Console/Commands/CheckModpackUpdates.php" ...
|
||||
cp "$EXT_DIR/app/Http/Controllers/ModpackAPIController.php" ...
|
||||
# Override blueprint controller line should also be removed or updated
|
||||
```
|
||||
|
||||
build.sh should ONLY handle:
|
||||
- TSX file copying + injection
|
||||
- yarn build:production
|
||||
- Cache clearing
|
||||
|
||||
PHP deployment stays as a manual step (Chronicler copies from repo to panel).
|
||||
|
||||
*— Chronicler #84, The Meridian*
|
||||
22
docs/code-bridge/archive/MSG-2026-04-13-docblock-final.md
Normal file
22
docs/code-bridge/archive/MSG-2026-04-13-docblock-final.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Chronicler Dispatch — Fix */6 in repo PERMANENTLY
|
||||
|
||||
**Date:** April 13, 2026
|
||||
**From:** Chronicler #84 — The Meridian
|
||||
**To:** Code
|
||||
|
||||
---
|
||||
|
||||
The `*/6` docblock comment in `CheckModpackUpdates.php` line 16 keeps coming back because build.sh copies from the source file which still has it.
|
||||
|
||||
Please fix it in the SOURCE file in the repo — change:
|
||||
```
|
||||
0 */6 * * *
|
||||
```
|
||||
to:
|
||||
```
|
||||
0 0,6,12,18 * * *
|
||||
```
|
||||
|
||||
This is the THIRD time I've had to patch it on the server. Fix it at the source so it stops coming back.
|
||||
|
||||
*— Chronicler #84, The Meridian*
|
||||
9
scripts/pre-commit-hook.sh
Normal file
9
scripts/pre-commit-hook.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
# Pre-commit hook: PHP syntax check
|
||||
# Install: cp scripts/pre-commit-hook.sh .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
|
||||
|
||||
FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep ".php$")
|
||||
for FILE in $FILES; do
|
||||
php -l "$FILE" || { echo "Aborting commit: PHP syntax error in $FILE"; exit 1; }
|
||||
done
|
||||
exit 0
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user