/** * When several dev servers expose preview URLs, prefer the one that looks like * the user-facing web app (monorepo-safe ordering for anatomy + tool listings). */ function previewFrontendRank(row: { name: string; command: string }): number { const n = row.name.toLowerCase(); const c = row.command.toLowerCase(); const blob = `${n} ${c}`; if ( /\bfrontend\b/.test(blob) || blob.includes("/frontend/") || blob.includes("\\frontend\\") || blob.includes("/apps/web") || blob.includes("apps/web/") || blob.includes("/packages/web") || blob.includes("packages/web/") ) { return 0; } if ( blob.includes("next dev") || blob.includes("vite") || blob.includes("nuxt") || blob.includes("remix dev") || blob.includes("astro dev") || blob.includes("npm run dev") || blob.includes("pnpm dev") || blob.includes("yarn dev") ) { return 1; } if (/\b(web|ui|client)\b/.test(n)) { return 2; } if ( /\bapi\b/.test(n) || /\bbackend\b/.test(n) || blob.includes("fastapi") || blob.includes("uvicorn") || blob.includes("gunicorn") || blob.includes("django") || (blob.includes("rails ") && blob.includes("server")) ) { return 10; } return 5; } function startedAtMs(startedAt: string | Date): number { return typeof startedAt === "string" ? new Date(startedAt).getTime() : startedAt.getTime(); } export function compareDevPreviewFrontendFirst< T extends { name: string; command: string; port: number; started_at: string | Date; }, >(a: T, b: T): number { const pa = previewFrontendRank(a); const pb = previewFrontendRank(b); if (pa !== pb) return pa - pb; const ax = a.port === 3000 ? 0 : a.port; const bx = b.port === 3000 ? 0 : b.port; if (ax !== bx) return ax - bx; return startedAtMs(b.started_at) - startedAtMs(a.started_at); } export function sortDevPreviewsFrontendFirst< T extends { name: string; command: string; port: number; started_at: string | Date; }, >(rows: T[]): T[] { return [...rows].sort(compareDevPreviewFrontendFirst); }