diff --git a/app/scripts/ci.mjs b/app/scripts/ci.mjs new file mode 100644 index 0000000..adbd36f --- /dev/null +++ b/app/scripts/ci.mjs @@ -0,0 +1,46 @@ +#!/usr/bin/env node + +import { execSync } from 'node:child_process'; +const passed = []; +const failed = []; + +function run(name, cmd, opts = {}) { + process.stdout.write(`\nā–¶ ${name}… `); + try { + execSync(cmd, { stdio: opts.silent ? 'pipe' : 'inherit', timeout: opts.timeout ?? 120_000 }); + passed.push(name); + console.log('āœ“'); + } catch (e) { + failed.push(name); + console.log('āœ—'); + if (!opts.continue) { + console.error(`\nAborting: ${name} failed.`); + process.exit(1); + } + } +} + +console.log('=== Teknav CI Pipeline ===\n'); + +// Backend checks +run('Backend TypeScript build', 'cd backend && npx tsc --noEmit', { continue: true }); +run('Backend unit tests', 'cd backend && node --test tests/unit-logic.test.mjs', { continue: true }); + +// Frontend checks +run('Frontend Vitest tests', 'npx vitest run', { continue: true, timeout: 60_000 }); +run('Frontend Vite build', 'npm run build', { continue: true, timeout: 300_000 }); +run('No CDN URLs in dist', 'node scripts/check-no-cdn.mjs', { continue: true }); +run('Responsive layout check', 'node scripts/check-responsive.mjs', { continue: true }); +run('Production readiness', 'npm run preflight:prod', { continue: true }); + +// Summary +console.log('\n=== CI Summary ==='); +console.log(`Passed: ${passed.length}`); +console.log(`Failed: ${failed.length}`); +if (failed.length > 0) { + console.log('Failed steps:', failed.join(', ')); + process.exit(1); +} else { + console.log('All checks passed!'); + process.exit(0); +}