/** * Generates line-by-line annotated copies of every src/ file. * Output: docs/annotated/src/** (mirrors src/ structure) * Run: node docs/annotate-all.mjs */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.join(__dirname, '..'); const SRC = path.join(ROOT, 'src'); const OUT = path.join(__dirname, 'annotated', 'src'); function explainLine(line, lineNum, relPath) { const t = line.trim(); const indent = line.match(/^(\s*)/)[1]; if (t === '') return `${indent}// (blank line — separates logical blocks for readability)`; if (t.startsWith('/**') || t.startsWith('*') || t.startsWith('*/')) return `${indent}// Block comment — documents the file or function below`; if (t.startsWith('//')) return line; if (t === "'use client';" || t === '"use client";') return `${indent}// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)`; if (t.startsWith('import type ')) return `${indent}// Type-only import — erased at compile time; no JavaScript bundle cost`; if (t.startsWith('import ')) { if (t.includes("from 'react'") || t.includes('from "react"')) return `${indent}// Import React — core UI library (components, hooks, JSX)`; if (t.includes('next/link')) return `${indent}// Next.js Link — fast client-side navigation without full page reload`; if (t.includes('next/image')) return `${indent}// Next.js Image — optimized images (lazy load, WebP/AVIF)`; if (t.includes('next/navigation')) return `${indent}// Next.js App Router hooks — useRouter, useParams, useSearchParams`; if (t.includes('next/font')) return `${indent}// Self-hosted Google fonts — better performance than external CSS`; if (t.includes('lucide-react')) return `${indent}// Lucide icons — lightweight SVG icon components`; if (t.includes('zustand')) return `${indent}// Zustand — simple global state store (cart, auth, locale)`; if (t.includes('@/')) return `${indent}// Import project module (@/ alias = src/ folder in tsconfig)`; if (t.includes("'./") || t.includes('"./')) return `${indent}// Import from a relative file in the same project`; return `${indent}// Import external package or local module`; } if (t.startsWith('export type ') || t.startsWith('export interface ')) return `${indent}// Export TypeScript type — defines data shape used across the app`; if (t.startsWith('interface ')) return `${indent}// TypeScript interface — contract for object properties and methods`; if (t.startsWith('type ')) return `${indent}// TypeScript type alias — union or shorthand for complex types`; if (t.startsWith('export default function')) return `${indent}// Default export — main component/page Next.js or other files import`; if (t.startsWith('export function')) return `${indent}// Named export — utility function other files can import`; if (t.startsWith('export const ')) return `${indent}// Named export constant — shared config/data imported elsewhere`; if (t.startsWith('const ') && t.includes('= create<')) return `${indent}// Create Zustand store — global state hook (useXxxStore)`; if (t.startsWith('const ') && t.includes('useState')) return `${indent}// React useState — local component state that triggers re-render on change`; if (t.startsWith('const ') && t.includes('useMemo')) return `${indent}// React useMemo — cache expensive computed value until dependencies change`; if (t.startsWith('const ') && t.includes('useCallback')) return `${indent}// React useCallback — stable function reference for useEffect/useMemo deps`; if (t.startsWith('const ') && t.includes('useEffect')) return `${indent}// React useEffect — run side effect after render (sync URL, redirect, etc.)`; if (t.startsWith('const ') && t.includes('useRouter')) return `${indent}// Next.js router — programmatic navigation (router.push)`; if (t.startsWith('const ') && t.includes('useSearchParams')) return `${indent}// Read URL query string (?category=beef&q=steak)`; if (t.startsWith('const ') && t.includes('useParams')) return `${indent}// Read dynamic route segment ([slug] from URL)`; if (t.startsWith('const ') && t.includes('useTranslation')) return `${indent}// Custom hook — returns t() translator and current locale`; if (t.startsWith('const ') && t.includes('useCartStore') || t.startsWith('const ') && t.includes('useAuthStore') || t.startsWith('const ') && t.includes('useWishlistStore') || t.startsWith('const ') && t.includes('useLocaleStore')) return `${indent}// Zustand selector — subscribe to slice of global store`; if (t.startsWith('function ')) return `${indent}// Function declaration — reusable logic in this file`; if (t.startsWith('return (')) return `${indent}// Return JSX — describes UI tree React renders to the DOM`; if (t.startsWith('return ')) return `${indent}// Return value from function`; if (t.startsWith('if (') || t.startsWith('} else if (')) return `${indent}// Conditional branch — different behavior based on runtime value`; if (t.startsWith('switch (')) return `${indent}// Switch — multiple branches on one variable (e.g. sort order)`; if (t.startsWith('case ')) return `${indent}// Switch case — handle one specific value`; if (t.startsWith('default:')) return `${indent}// Switch default — fallback when no case matches`; if (t.startsWith('useEffect(')) return `${indent}// Side effect hook — runs after paint; deps array controls when it re-runs`; if (t.includes('.map(')) return `${indent}// Array.map — transform each item (often render a list of components)`; if (t.includes('.filter(')) return `${indent}// Array.filter — keep items matching condition (search, category)`; if (t.includes('.sort(')) return `${indent}// Array.sort — reorder items (price, name, featured)`; if (t.includes('.find(')) return `${indent}// Array.find — get first matching item or undefined`; if (t.includes('.reduce(')) return `${indent}// Array.reduce — accumulate single value (cart total, item count)`; if (t.startsWith('<') && !t.startsWith('<>')) return `${indent}// JSX element — HTML-like tag becomes React component in browser`; if (t.startsWith('<>') || t === '') return `${indent}// React Fragment — group elements without extra wrapper DOM node`; if (t.startsWith('{/*')) return `${indent}// JSX comment — not visible in browser`; if (t.startsWith('className=')) return `${indent}// Tailwind CSS utility classes — styling (colors, spacing, layout)`; if (t.startsWith('href=')) return `${indent}// Link target URL — internal route or external https://`; if (t.startsWith('onClick=')) return `${indent}// Click handler — runs when user clicks (must be client component)`; if (t.startsWith('onChange=')) return `${indent}// Change handler — runs when input/select value changes`; if (t.startsWith('aria-')) return `${indent}// Accessibility attribute — screen readers and assistive tech`; if (t.startsWith('}')) return `${indent}// Closing brace — end of block (function, if, object, JSX)`; if (t.startsWith(']') || t.startsWith('];')) return `${indent}// End of array literal`; if (t.endsWith(',') && !t.includes('//')) return `${indent}// Property or array item — trailing comma allowed in TypeScript`; if (relPath.includes('locales/') && t.match(/^\s{2}\w+:/)) return `${indent}// i18n translation section — keys used by t('section.key') in components`; if (relPath.includes('locales/') && t.includes("name:") || relPath.includes('locales/') && t.includes("title:")) return `${indent}// Translated UI string for current language (sv / en / ur)`; if (relPath.includes('products.ts')) return `${indent}// Product catalog entry — demo data shown in shop and product pages`; if (t.includes('persist(')) return `${indent}// Zustand persist — save store to localStorage between visits`; return `${indent}// Line ${lineNum}: ${t.length > 60 ? t.slice(0, 57) + '...' : t || 'code'}`; } function annotateFile(content, relPath) { const lines = content.split('\n'); const ext = relPath.endsWith('.tsx') ? 'tsx' : 'ts'; const header = [ `/**`, ` * ANNOTATED COPY — every line explained`, ` * Source: src/${relPath}`, ` * NOT used by the app — read this to learn how the real file works`, ` */`, '', ]; const body = lines.map((line, i) => { const comment = explainLine(line, i + 1, relPath); if (line.trim().startsWith('//') && comment === line) return line; if (line.trim() === '' && comment.includes('blank')) return comment; return `${comment}\n${line}`; }); return header.join('\n') + body.join('\n') + '\n'; } function walk(dir, base = '') { const entries = fs.readdirSync(dir, { withFileTypes: true }); let count = 0; for (const e of entries) { const rel = base ? `${base}/${e.name}` : e.name; const full = path.join(dir, e.name); if (e.isDirectory()) { count += walk(full, rel); } else if (/\.(ts|tsx)$/.test(e.name)) { const content = fs.readFileSync(full, 'utf8'); const annotated = annotateFile(content, rel); const outName = e.name.replace(/\.(tsx?)$/, '.annotated.$1'); const outDir = path.join(OUT, base); fs.mkdirSync(outDir, { recursive: true }); fs.writeFileSync(path.join(outDir, outName), annotated); count++; } } return count; } // Remove old flat annotated files (replaced by src/ mirror) const oldFlat = path.join(__dirname, 'annotated'); for (const f of fs.readdirSync(oldFlat)) { if (f.endsWith('.annotated.ts') || f.endsWith('.annotated.tsx')) { if (!f.startsWith('0')) continue; try { fs.unlinkSync(path.join(oldFlat, f)); } catch (_) {} } } const n = walk(SRC); console.log(`Annotated ${n} files → docs/annotated/src/`);