Files
projectbrain-Public/scripts/check-frontend-runtime-readiness.mjs
T

84 lines
5.2 KiB
JavaScript

#!/usr/bin/env node
import { readFileSync, existsSync } from 'node:fs';
const checks = [];
const fail = (message) => checks.push({ ok: false, message });
const pass = (message) => checks.push({ ok: true, message });
const read = (path) => readFileSync(path, 'utf8');
const boundaryPath = 'src/shared/components/RuntimeErrorBoundary.tsx';
if (existsSync(boundaryPath)) {
const boundary = read(boundaryPath);
boundary.includes('componentDidCatch') ? pass('RuntimeErrorBoundary catches render crashes') : fail('RuntimeErrorBoundary must implement componentDidCatch');
boundary.includes('Frontend runtime guard') ? pass('RuntimeErrorBoundary renders user-facing fallback') : fail('RuntimeErrorBoundary needs visible fallback copy');
boundary.includes('Copy diagnostics') && boundary.includes('navigator.clipboard.writeText')
? pass('RuntimeErrorBoundary can copy diagnostics')
: fail('RuntimeErrorBoundary must support one-click diagnostic copy');
} else {
fail('RuntimeErrorBoundary component is missing');
}
const app = read('src/App.tsx');
app.includes('<RuntimeErrorBoundary>') ? pass('App wraps routes in RuntimeErrorBoundary') : fail('App must wrap routes in RuntimeErrorBoundary');
const routes = read('src/app/routes.tsx');
const projectBrainClient = read('src/shared/api/projectBrainClient.ts');
projectBrainClient.includes('const useRealClient = false')
? pass('Synchronous projectBrainClient cannot call async realClient')
: fail('projectBrainClient must not route sync selectors to async realClient');
projectBrainClient.includes('Real HTTP calls are async')
? pass('Sync/real client boundary is documented')
: fail('Sync/real client boundary must be documented');
const projectContext = read('src/shared/contexts/ProjectContext.tsx');
projectContext.includes('toProjectArray(getProjects())')
? pass('ProjectContext normalizes project arrays')
: fail('ProjectContext must normalize getProjects result before .map');
projectContext.includes('toFeatureGroups(project)')
? pass('ProjectContext guards featureGroups access')
: fail('ProjectContext must guard featureGroups access');
projectContext.includes('realClient.projects.listProjects()') && projectContext.includes('reloadProjectData')
? pass('ProjectContext can hydrate projects from the real backend')
: fail('ProjectContext must expose a real backend project hydration path');
projectContext.includes('realClient.projects.createProject') && projectContext.includes('Project could not be saved to the backend')
? pass('ProjectContext can create projects through the real backend')
: fail('ProjectContext must create projects through the real backend in real mode');
projectContext.includes('realClient.workspace.updateActiveContextPack') && projectContext.includes('Active context pack could not be saved to the backend')
? pass('ProjectContext can persist active context pack changes')
: fail('ProjectContext must persist active context pack changes in real mode');
projectContext.includes('realClient.workspace.getWorkspaceState(projectId)') && projectContext.includes('realClient.projectDNA.getProjectDNA(projectId)')
? pass('ProjectContext hydrates workspace and DNA with project data')
: fail('ProjectContext must hydrate workspace and DNA in real project mode');
projectContext.includes('projectDataMode: "local" | "real"') && projectContext.includes('projectDataLoading') && projectContext.includes('projectDataError')
? pass('ProjectContext exposes data mode, loading and error state')
: fail('ProjectContext must expose project data mode, loading and error state');
routes.includes('ProjectRouteSync') && routes.includes('routeProjectExists') && routes.includes('setActiveProjectId(routeProjectId)')
? pass('Project routes follow known project route changes')
: fail('Project route sync must follow known project route changes without activating unknown slugs');
const projectsPage = read('src/features/projects/pages/Projects.tsx');
projectsPage.includes('Array.isArray(project.featureGroups)')
? pass('Projects page guards featureGroups before stats')
: fail('Projects page must guard featureGroups before stats');
projectsPage.includes('project.workspaceState?.focusAreas?.length')
? pass('Projects page guards focusAreas length')
: fail('Projects page must guard focusAreas length');
projectsPage.includes('Database projects') && projectsPage.includes('Reload projects')
? pass('Projects page exposes real data mode and reload affordance')
: fail('Projects page must expose real data mode and reload affordance');
const workspacePage = read('src/features/projects/pages/Workspace.tsx');
workspacePage.includes('Database-backed project context') && workspacePage.includes('Reload project')
? pass('Workspace exposes real project context and reload affordance')
: fail('Workspace must expose real project context and reload affordance');
const failures = checks.filter((check) => !check.ok);
if (failures.length) {
console.error('ProjectBrain frontend runtime readiness: FAIL');
for (const check of checks) console.error(`${check.ok ? '✓' : '✗'} ${check.message}`);
process.exit(1);
}
console.log('ProjectBrain frontend runtime readiness: PASS');
for (const check of checks) console.log(`✓ ${check.message}`);