#!/usr/bin/env node /** * Fixes Internal Server Error (Server/Client boundary + DI init), then builds. * Run from project root: node fix-and-build.mjs */ import { spawnSync } from 'child_process'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const root = path.dirname(fileURLToPath(import.meta.url)); const logPath = path.join(root, 'agent-build-log.txt'); const HEADER_CONSTANTS = `/** Shared header layout constants — safe for Server Components (no 'use client'). */ export const LANGUAGE_BANNER_HEIGHT = 48; export const NAVBAR_HEIGHT = 68; export const HEADER_HEIGHT = LANGUAGE_BANNER_HEIGHT + NAVBAR_HEIGHT; // 116px export const HEADER_HEIGHT_MOBILE = HEADER_HEIGHT; export const HEADER_OFFSET_VAR = '--header-height'; /** @deprecated Use HEADER_HEIGHT */ export const headerHeight = HEADER_HEIGHT; `; const HEADER_SPACER = `import { HEADER_HEIGHT } from '@/lib/header-constants'; /** Pushes page content below the fixed language banner + navbar. */ export function HeaderSpacer() { return
; } `; function log(msg) { console.log(msg); fs.appendFileSync(logPath, msg + '\n'); } function read(file) { try { return fs.readFileSync(path.join(root, file), 'utf8'); } catch { return null; } } function write(file, content) { const full = path.join(root, file); fs.mkdirSync(path.dirname(full), { recursive: true }); fs.writeFileSync(full, content); log(` wrote ${file}`); } function walk(dir, acc = []) { const full = path.join(root, dir); if (!fs.existsSync(full)) return acc; for (const name of fs.readdirSync(full)) { if (name === 'node_modules' || name === '.next' || name === '.git') continue; const rel = path.join(dir, name); const stat = fs.statSync(path.join(root, rel)); if (stat.isDirectory()) walk(rel, acc); else if (/\.(tsx?|jsx?|mjs|css)$/.test(name)) acc.push(rel); } return acc; } fs.writeFileSync(logPath, `=== fix-and-build ${new Date().toISOString()} ===\n`); const fixes = []; // Always sync server-safe header constants const prevLib = read('lib/header-constants.ts'); if (prevLib !== HEADER_CONSTANTS) { write('lib/header-constants.ts', HEADER_CONSTANTS); fixes.push('Synced lib/header-constants.ts (116px header)'); } // Force-fix HeaderSpacer if it imports from client LanguageSwitcher for (const spacerPath of ['components/HeaderSpacer.tsx', 'components/layout/HeaderSpacer.tsx']) { const spacer = read(spacerPath); if (!spacer) continue; if (/LanguageSwitcher/.test(spacer)) { write(spacerPath, HEADER_SPACER); fixes.push(`${spacerPath}: replaced — imports from @/lib/header-constants`); } } const files = walk('.'); for (const file of files) { let content = read(file); if (!content) continue; const original = content; const isClient = /^\s*['"]use client['"]/.test(content); // Any file importing layout constants from LanguageSwitcher → header-constants if (/LanguageSwitcher/.test(content) && /HEADER|LANGUAGE_BANNER|NAVBAR|headerHeight/i.test(content)) { content = content .replace( /import\s*\{([^}]+)\}\s*from\s*['"][^'"]*LanguageSwitcher['"];?\s*\n/g, (m, imports) => { const names = imports .split(',') .map((s) => s.trim()) .filter(Boolean) .filter((n) => /HEADER|LANGUAGE_BANNER|NAVBAR|headerHeight/i.test(n)); if (!names.length) return m; return `import { ${names.join(', ')} } from '@/lib/header-constants';\n`; } ) .replace( /import\s*\{([^}]+)\}\s*from\s*['"]\.\/LanguageSwitcher['"];?\s*\n/g, (m, imports) => { const names = imports .split(',') .map((s) => s.trim()) .filter(Boolean) .filter((n) => /HEADER|LANGUAGE_BANNER|NAVBAR|headerHeight/i.test(n)); if (!names.length) return m; return `import { ${names.join(', ')} } from '@/lib/header-constants';\n`; } ); if (content !== original) { fixes.push(`${file}: header constants from @/lib/header-constants`); } } // LanguageSwitcher: move exported layout constants to lib if (/LanguageSwitcher/i.test(file) && isClient) { const constExports = [...content.matchAll(/^export const (\w+)\s*=/gm)].map((m) => m[1]); const headerConsts = constExports.filter((n) => /HEADER|LOCALE|SUPPORTED|DEFAULT_LOCALE|LANGUAGE_BANNER|NAVBAR|headerHeight/i.test(n) ); if (headerConsts.length) { let lib = read('lib/header-constants.ts') || HEADER_CONSTANTS; for (const name of headerConsts) { const block = content.match(new RegExp(`export const ${name}\\s*=\\s*[^;]+;`, 'm')); if (block && !lib.includes(`export const ${name}`)) { lib += `\n${block[0]}`; } content = content.replace(new RegExp(`export const ${name}\\s*=\\s*[^;]+;\\s*\\n?`, 'm'), ''); } const layoutConsts = headerConsts.filter((n) => /HEADER|LANGUAGE_BANNER|NAVBAR|headerHeight/i.test(n) ); if (layoutConsts.length && !/from ['"]@\/lib\/header-constants['"]/.test(content)) { content = `import { ${layoutConsts.join(', ')} } from '@/lib/header-constants';\n` + content; } write('lib/header-constants.ts', lib.trim() + '\n'); fixes.push(`${file}: moved ${headerConsts.join(', ')} to lib/header-constants.ts`); } } // SignatureMenuMarquee: client boundary if (/SignatureMenuMarquee/i.test(file) && !isClient) { const needsClient = /\b(useState|useEffect|useRef|useCallback|useMemo|onClick|onMouse|framer-motion|useInView)\b/.test( content ); if (needsClient) { content = `'use client';\n\n${content.replace(/^\s*['"]use client['"];?\s*\n?/, '')}`; fixes.push(`${file}: added 'use client' directive`); } } // container.ts: lazy init if (file.endsWith('infrastructure/di/container.ts') || file.endsWith('lib/di/container.ts')) { if (/export const container\s*=/.test(content) && !/function getContainer/.test(content)) { content = content.replace( /export const container\s*=\s*(\w+)\(\);?/, `let _container = null;\n\nexport function getContainer() {\n if (!_container) _container = $1();\n return _container;\n}\n\n/** @deprecated Prefer getContainer() */\nexport const container = new Proxy({}, {\n get(_, prop) {\n return getContainer()[prop];\n },\n});` ); fixes.push(`${file}: lazy container init via getContainer()`); } content = content.replace( /if\s*\(\s*!process\.env\.(\w+)\s*\)\s*throw new Error\([^)]+\);/g, `if (!process.env.$1) {\n if (process.env.NODE_ENV === 'production') throw new Error('Missing env: $1');\n }` ); } // App pages: getContainer() instead of eager container if (/^app\/.*\.(tsx|ts)$/.test(file) && /container/.test(content)) { if (/from ['"][^'"]*\/di\/container['"]/.test(content) && !/getContainer/.test(content)) { content = content.replace( /import\s*\{([^}]*)\bcontainer\b([^}]*)\}\s*from\s*['"]([^'"]*\/di\/container)['"]/, (m, before, after, mod) => { const names = `${before}${after}` .split(',') .map((s) => s.trim()) .filter(Boolean) .filter((n) => n !== 'container'); const extra = names.length ? `, ${names.join(', ')}` : ''; const importPath = mod.startsWith('@') ? mod : `@/${mod.replace(/^\//, '')}`; return `import { getContainer${extra} } from '${importPath}'`; } ); content = content.replace(/\bcontainer\./g, 'getContainer().'); if (content !== original) fixes.push(`${file}: use getContainer() instead of eager container`); } } // globals.css: --header-height if (file === 'app/globals.css' || file.endsWith('/globals.css')) { if (/--header-height:\s*\d+px/.test(content)) { content = content.replace(/--header-height:\s*\d+px/g, '--header-height: 116px'); if (content !== original) fixes.push(`${file}: --header-height set to 116px`); } else if (!content.includes('--header-height')) { content = `:root {\n --header-height: 116px;\n}\n\n` + content; fixes.push(`${file}: added --header-height: 116px`); } } if (content !== original) write(file, content); } // Clear stale Next.js cache const nextDir = path.join(root, '.next'); if (fs.existsSync(nextDir)) { fs.rmSync(nextDir, { recursive: true, force: true }); fixes.push('Removed .next cache'); } log('\n--- Fixes ---'); if (!fixes.length) log('(no automatic fixes applied)'); else fixes.forEach((f) => log(` • ${f}`)); log('\n--- npm run build ---\n'); const build = spawnSync('npm', ['run', 'build'], { cwd: root, encoding: 'utf8', maxBuffer: 20 * 1024 * 1024, shell: true, }); if (build.stdout) { process.stdout.write(build.stdout); fs.appendFileSync(logPath, build.stdout); } if (build.stderr) { process.stderr.write(build.stderr); fs.appendFileSync(logPath, build.stderr); } const ok = build.status === 0; log(ok ? '\nBUILD PASSED — run: npm run dev' : `\nBUILD FAILED (exit ${build.status})`); process.exit(ok ? 0 : 1);