84 lines
5.2 KiB
JavaScript
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}`);
|