import fs from 'node:fs'; import path from 'node:path'; const root = process.cwd(); const files = [ 'src/TaxBaik.Web/Program.cs', 'src/TaxBaik.Web.Client/Components/Admin/App.razor', 'src/TaxBaik.Web.Client/Components/Admin/Routes.razor', 'src/TaxBaik.Web.Client/Components/Admin/RedirectToLogin.razor', 'tests/e2e/public-smoke.spec.ts', 'tests/e2e/admin-smoke.spec.ts', 'tests/e2e/route-isolation.spec.ts' ]; const required = [ { file: 'src/TaxBaik.Web/Program.cs', pattern: 'app.MapFallbackToFile("admin/{*path:nonfile}", "admin/index.html");', message: '관리자 SPA 폴백은 admin/index.html로만 연결되어야 합니다.' }, { file: 'tests/e2e/route-isolation.spec.ts', pattern: 'public home does not boot the admin app', message: '공용/관리자 분리 하네스가 필요합니다.' }, { file: 'tests/e2e/route-isolation.spec.ts', pattern: 'admin login boots the admin app under the admin base path', message: '관리자 base path 하네스가 필요합니다.' } ]; let failed = false; for (const entry of required) { const fullPath = path.join(root, entry.file); const text = fs.readFileSync(fullPath, 'utf8'); if (!text.includes(entry.pattern)) { console.error(`[route-lint] FAIL ${entry.file}: missing "${entry.pattern}"`); console.error(` ${entry.message}`); failed = true; } } const adminFiles = files .filter(file => file.includes('/Admin/') || file.includes('admin-smoke') || file.includes('route-isolation')); for (const file of adminFiles) { const fullPath = path.join(root, file); const text = fs.readFileSync(fullPath, 'utf8'); if (text.includes('ToBaseRelativePath(Navigation.Uri)')) { console.error(`[route-lint] FAIL ${file}: unsafe ToBaseRelativePath(Navigation.Uri) usage detected.`); failed = true; } if (file === 'src/TaxBaik.Web.Client/Components/Admin/App.razor') { if (text.includes('