#!/usr/bin/env node // frontend/tsconfig.sonar.json is a standalone copy of frontend/tsconfig.json // that exists only so SonarQube 9.9 can build a TypeScript program — see #67. // It cannot use `extends`, because the analyser's bundled compiler validates the // base file while reading it and rejects "moduleResolution": "bundler" before // any override applies. // // A copy drifts. When it does, the scan does not fail: it goes back to skipping // every frontend file and still reports EXECUTION SUCCESS, which is exactly the // silent failure #67 was about. So the copy is checked rather than trusted. const fs = require('fs'); const path = require('path'); const dir = path.join(__dirname, '..', 'frontend'); const REAL = path.join(dir, 'tsconfig.json'); const SONAR = path.join(dir, 'tsconfig.sonar.json'); // The one option the copy is allowed to differ on — the whole reason it exists. const ALLOWED_DIFF = 'moduleResolution'; // Strips comments and the "//" documentation key so the two are compared on // their actual settings. function load(file) { const text = fs.readFileSync(file, 'utf-8').replace(/^\s*\/\/.*$/gm, ''); const parsed = JSON.parse(text); delete parsed['//']; return parsed; } const real = load(REAL); const sonar = load(SONAR); const problems = []; const realOpts = real.compilerOptions || {}; const sonarOpts = sonar.compilerOptions || {}; for (const key of new Set([...Object.keys(realOpts), ...Object.keys(sonarOpts)])) { if (key === ALLOWED_DIFF) continue; const a = JSON.stringify(realOpts[key]); const b = JSON.stringify(sonarOpts[key]); if (a !== b) problems.push(`compilerOptions.${key}: tsconfig.json has ${a}, tsconfig.sonar.json has ${b}`); } if (JSON.stringify(real.include) !== JSON.stringify(sonar.include)) { problems.push(`include: tsconfig.json has ${JSON.stringify(real.include)}, tsconfig.sonar.json has ${JSON.stringify(sonar.include)}`); } // A copy that no longer overrides the option it exists to override is worse than // no copy at all — the scan would silently fall back to skipping the frontend. if (sonarOpts[ALLOWED_DIFF] === 'bundler' || sonarOpts[ALLOWED_DIFF] === undefined) { problems.push(`compilerOptions.${ALLOWED_DIFF} in tsconfig.sonar.json must be a value SonarQube 9.9 accepts ('node', 'classic', 'node16', 'nodenext'), not ${JSON.stringify(sonarOpts[ALLOWED_DIFF])}`); } if (problems.length) { console.error('frontend/tsconfig.sonar.json has drifted from frontend/tsconfig.json:\n'); for (const problem of problems) console.error(` - ${problem}`); console.error(`\nBring them back into step. Everything except "${ALLOWED_DIFF}" must match, or SonarQube analyses the frontend against settings the app is not built with. See #67.`); process.exit(1); } console.log(`frontend/tsconfig.sonar.json matches tsconfig.json (differing only on ${ALLOWED_DIFF}, as intended).`);