#!/usr/bin/env python3 """ Nuclear fix: make every Payload-touching page force-dynamic, remove all generateStaticParams from those pages, and reduce Next.js build workers to prevent DB pool exhaustion. """ import os, re, glob def read(path): with open(path, 'r') as f: return f.read() def write(path, content): with open(path, 'w') as f: f.write(content) def remove_generate_static_params(content): """Remove the entire generateStaticParams function from a file.""" # Match: export async function generateStaticParams() { ... } # Using a simple brace-counting approach pattern = r'export async function generateStaticParams\(\)' match = re.search(pattern, content) if not match: return content, False start = match.start() # Find the opening brace brace_start = content.index('{', match.end()) depth = 0 i = brace_start while i < len(content): if content[i] == '{': depth += 1 elif content[i] == '}': depth -= 1 if depth == 0: end = i + 1 break i += 1 # Remove the function (and any leading newlines before it) before = content[:start].rstrip('\n') + '\n' after = content[end:].lstrip('\n') return before + after, True def ensure_force_dynamic(content): """Add export const dynamic = 'force-dynamic' if not present.""" if "dynamic = 'force-dynamic'" in content or 'dynamic = "force-dynamic"' in content: return content, False # Remove conflicting revalidate settings content = re.sub(r"export const revalidate = \d+\n?", '', content) # Add after the last import line lines = content.split('\n') last_import = 0 for i, line in enumerate(lines): if line.startswith('import '): last_import = i lines.insert(last_import + 1, '') lines.insert(last_import + 2, "export const dynamic = 'force-dynamic'") lines.insert(last_import + 3, 'export const dynamicParams = true') return '\n'.join(lines), True # ── Find all page.tsx files that use Payload ────────────────────────────── print("=== Nuclear Payload Build Fix ===\n") page_files = glob.glob('src/app/**/page.tsx', recursive=True) print(f"Found {len(page_files)} page files total\n") payload_pages = [] for path in page_files: content = read(path) if 'payload-config' in content or 'getPayload' in content or 'getCachedGlobal' in content: payload_pages.append(path) print(f"→ {len(payload_pages)} pages use Payload:\n") for p in payload_pages: print(f" {p}") print() # ── Fix each Payload page ───────────────────────────────────────────────── for path in payload_pages: content = read(path) changed = False # Remove generateStaticParams new_content, removed = remove_generate_static_params(content) if removed: print(f" ✓ Removed generateStaticParams: {path}") changed = True content = new_content # Ensure force-dynamic new_content, added = ensure_force_dynamic(content) if added: print(f" ✓ Added force-dynamic: {path}") changed = True content = new_content if changed: write(path, content) else: print(f" - Already OK: {path}") # ── Update next.config to limit workers ────────────────────────────────── print("\n→ Looking for next.config...") next_configs = glob.glob('next.config.*') if next_configs: nc_path = next_configs[0] nc = read(nc_path) if 'workerThreads' not in nc and 'cpus' not in nc: # Add experimental config to limit parallel workers nc = nc.replace( 'const nextConfig', '/** @type {import("next").NextConfig} */\nconst nextConfig' ) if '/** @type' not in nc else nc # Insert experimental block before the closing of nextConfig object if 'experimental:' not in nc: nc = re.sub( r'(const nextConfig\s*=\s*\{)', r'\1\n experimental: {\n workerThreads: false,\n cpus: 1,\n },', nc ) write(nc_path, nc) print(f" ✓ Limited build workers in {nc_path}") else: print(f" - experimental block already exists in {nc_path}, add cpus: 1 manually") else: print(f" - Worker limits already set in {nc_path}") else: # Create a basic next.config.js print(" - No next.config found, creating next.config.js with worker limit...") # Try to find and read existing config existing = None for name in ['next.config.js', 'next.config.mjs', 'next.config.ts']: if os.path.exists(name): existing = name break if not existing: # Check if there's a withPayload wrapper we need to preserve # Write a minimal config config_content = '''import { withPayload } from '@payloadcms/next' /** @type {import('next').NextConfig} */ const nextConfig = { experimental: { workerThreads: false, cpus: 1, }, } export default withPayload(nextConfig) ''' write('next.config.js', config_content) print(" ✓ Created next.config.js") # ── Verify generateStaticParams is gone from all payload pages ──────────── print("\n→ Verifying...") remaining = [] for path in payload_pages: content = read(path) if 'generateStaticParams' in content: remaining.append(path) if remaining: print("\n ⚠ generateStaticParams still present in:") for p in remaining: print(f" {p}") print(" These need manual removal.") else: print(" ✓ No generateStaticParams remaining in Payload pages") # ── Check for any other pages with generateStaticParams ─────────────────── all_with_gsp = [] for path in page_files: content = read(path) if 'generateStaticParams' in content and path not in payload_pages: all_with_gsp.append(path) if all_with_gsp: print(f"\n ℹ Non-Payload pages with generateStaticParams (likely fine):") for p in all_with_gsp: print(f" {p}") print("\n=== Done! ===") print("\nNow run:") print(" rm -rf .next && npm run build")