const fs = require('fs'); const path = require('path'); // naive scan for @Controller and @Get/@Post/@Put/@Delete decorations function scanControllers(rootDir) { const results = []; function walk(dir) { for (const entry of fs.readdirSync(dir)) { const full = path.join(dir, entry); const stat = fs.statSync(full); if (stat.isDirectory()) walk(full); else if (entry.endsWith('.ts') && full.includes(path.join('controllers', 'adminapi'))) { const txt = fs.readFileSync(full, 'utf8'); const controllerPrefixMatch = txt.match(/@Controller\(['"]([^'\"]+)['"]\)/); const prefix = controllerPrefixMatch ? controllerPrefixMatch[1] : ''; const routeRegex = /@(Get|Post|Put|Delete)\(['"]([^'\"]*)['"]\)/g; let m; while ((m = routeRegex.exec(txt))) { const method = m[1].toUpperCase(); const suffix = m[2]; const fullPath = suffix ? `${prefix}/${suffix}` : prefix; results.push({ method, path: fullPath.replace(/\/:/g, '/:') }); } } } } walk(rootDir); return results; } function main() { const contract = JSON.parse( fs.readFileSync(path.join(__dirname, 'contracts', 'routes.json'), 'utf8'), ); const impl = scanControllers(path.join(__dirname, '..', 'wwjcloud', 'src', 'common')); function normalizePath(p) { // convert ${ var } or ${ params.var } to :var return String(p).replace(/\$\{\s*(?:params\.)?([a-zA-Z_][\w]*)\s*\}/g, ':$1'); } const toKey = (r) => `${r.method} ${normalizePath(r.path)}`; const contractSet = new Set(contract.map(toKey)); const implSet = new Set(impl.map(toKey)); const missing = contract.filter((r) => !implSet.has(toKey(r))); const extra = impl.filter((r) => !contractSet.has(toKey(r))); if (missing.length || extra.length) { console.error('Route contract mismatches found.'); if (missing.length) { console.error('Missing routes:'); for (const r of missing) console.error(` ${r.method} ${r.path}`); } if (extra.length) { console.error('Extra routes:'); for (const r of extra) console.error(` ${r.method} ${r.path}`); } process.exit(1); } console.log('All routes match contract.'); } main();