import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'; import path from 'node:path'; const root = process.cwd(); const checks = []; function record(level, label, detail = '') { checks.push({ level, label, detail }); } function pass(label, detail = '') { record('PASS', label, detail); } function warn(label, detail = '') { record('WARN', label, detail); } function fail(label, detail = '') { record('FAIL', label, detail); } function file(rel) { return path.join(root, rel); } function read(rel) { return readFileSync(file(rel), 'utf8'); } function requireFile(rel, label = rel) { if (existsSync(file(rel))) pass(label); else fail(label, `${rel} is missing`); } function requireText(rel, patterns, label = rel) { if (!existsSync(file(rel))) { fail(label, `${rel} is missing`); return; } const text = read(rel); for (const pattern of patterns) { if (typeof pattern === 'string' ? text.includes(pattern) : pattern.test(text)) { pass(`${label}: ${pattern.toString()}`); } else { fail(`${label}: ${pattern.toString()}`, 'not found'); } } } function walk(rel, predicate, hits = []) { const abs = file(rel); if (!existsSync(abs)) return hits; for (const name of readdirSync(abs)) { if (['node_modules', 'dist', '.npm-cache'].includes(name)) continue; const childRel = path.join(rel, name); const childAbs = file(childRel); const stat = statSync(childAbs); if (stat.isDirectory()) walk(childRel, predicate, hits); else if (predicate(childRel)) hits.push(childRel); } return hits; } requireFile('package.json'); requireFile('backend/package.json'); requireFile('backend/prisma/schema.prisma'); requireFile('.env.prod.example'); requireFile('backend/.env.example'); requireFile('nginx/nginx.conf.example'); requireFile('DEPLOY.md'); requireFile('SECURITY.md'); requireText('package.json', [ '"check:no-cdn"', '"check:responsive"', '"check:seo"', '"preflight:prod"', 'generate-article-pages', 'compress-dist', ], 'frontend scripts'); requireText('backend/package.json', [ '"build": "tsc"', '"test": "node --test tests/*.test.mjs"', '"prisma:generate"', '"prisma:apply"', '"seed"', ], 'backend scripts'); requireText('.env.prod.example', [ 'DATABASE_URL=', 'REDIS_URL=', 'SESSION_SECRET=', 'COOKIE_DOMAIN=".teknav.ir"', 'CORS_ORIGINS="https://www.teknav.ir,https://teknav.ir"', 'UPLOAD_DIR="/var/lib/teknav/uploads"', 'VAPID_PUBLIC_KEY=', 'VAPID_PRIVATE_KEY=', 'ZARINPAL_MERCHANT_ID=', 'STORAGE_BACKEND="local"', ], 'production env template'); requireText('backend/.env.example', [ 'SMTP_HOST=', 'SMS_PROVIDER=', 'VAPID_PUBLIC_KEY=', 'ZARINPAL_MERCHANT_ID=', 'STORAGE_BACKEND=local', ], 'backend env example'); requireText('nginx/nginx.conf.example', [ 'location = /ACCOUNTS.md', /location ~ \^\/api\/\(admin\/stream\|auth\/notifications\/stream\)\$/, /proxy_buffering\s+off;/, 'gzip_static on', ], 'nginx production template'); const accountLeaks = ['public/ACCOUNTS.md', 'dist/ACCOUNTS.md'].filter((rel) => existsSync(file(rel))); if (accountLeaks.length) fail('operator credentials not public', accountLeaks.join(', ')); else pass('operator credentials not public'); const migrationsDir = file('backend/prisma/migrations'); if (existsSync(migrationsDir)) pass('Prisma migrations directory present'); else warn('Prisma migrations directory absent', 'production currently relies on prisma:apply/db push fallback'); const engineArtifacts = walk('backend', (rel) => /(^|\/)(query_engine.*\.node(\.gz)?|schema-engine.*(\.gz)?)$/.test(rel), ); if (engineArtifacts.length) fail('no generated Prisma engine artifacts committed', engineArtifacts.join(', ')); else pass('no generated Prisma engine artifacts committed'); const backendTests = walk('backend/tests', (rel) => rel.endsWith('.test.mjs')); if (backendTests.length >= 2) pass('backend test coverage files', `${backendTests.length} test files`); else warn('backend test coverage files', `${backendTests.length} test file(s); add more domain tests`); const output = checks.map(({ level, label, detail }) => `${level} ${label}${detail ? ` - ${detail}` : ''}`); console.log(output.join('\n')); const failures = checks.filter((item) => item.level === 'FAIL'); const warnings = checks.filter((item) => item.level === 'WARN'); console.log(`\nProduction readiness: ${failures.length} failure(s), ${warnings.length} warning(s).`); if (failures.length) process.exit(1);