diff --git a/app/api/scan-repo/route.ts b/app/api/scan-repo/route.ts index 2b7d18d..b21b4ac 100644 --- a/app/api/scan-repo/route.ts +++ b/app/api/scan-repo/route.ts @@ -5,11 +5,14 @@ import type { RepoScanResponse, RepoScanSummary, RepoStructureSummary, + GitHubTreeItem, + PackageJson, } from "@/types/repo-scan" import { buildDependencyAnalysisTasks, hasDependencyDetectionRules } from "@/lib/stack-detection" import type { DependencyAnalysisTask } from "@/lib/stack-detection" -import { loadStackQuestionMetadata, normalizeConventionValue } from "@/lib/question-metadata" import { loadStackConventions } from "@/lib/conventions" +import { dependencyHas } from "@/lib/repo-scan/dependency-utils" +import { detectPythonTestingSignals } from "@/lib/repo-scan/python-testing-signals" import { inferStackFromScan } from "@/lib/scan-to-wizard" import { stackQuestion } from "@/lib/wizard-config" @@ -20,33 +23,6 @@ const JSON_HEADERS = { Accept: "application/vnd.github+json", } -interface GitHubTreeItem { - path: string - type: "blob" | "tree" | string -} - -interface PackageJson { - dependencies?: Record - devDependencies?: Record - peerDependencies?: Record - optionalDependencies?: Record - engines?: { node?: string } - workspaces?: string[] | { packages?: string[] } -} - -const dependencyHas = (pkg: PackageJson, names: string[]): boolean => { - const sources = [ - pkg.dependencies, - pkg.devDependencies, - pkg.peerDependencies, - pkg.optionalDependencies, - ] - - return sources.some((source) => - source ? names.some((name) => Object.prototype.hasOwnProperty.call(source, name)) : false, - ) -} - const isNullishOrEmpty = (value: unknown): value is null | undefined | "" => value === null || value === undefined || value === "" const extractRateLimitRemaining = (response: Response): number | null => { @@ -292,79 +268,6 @@ const detectTooling = async ( } } -type TestingConventionValues = { - unit: string[] - e2e: string[] -} - -const testingConventionCache = new Map() - -const getTestingConventionValues = async (stackId: string): Promise => { - const normalized = stackId.trim().toLowerCase() - if (testingConventionCache.has(normalized)) { - return testingConventionCache.get(normalized)! - } - - const metadata = await loadStackQuestionMetadata(normalized) - const values: TestingConventionValues = { - unit: metadata.answersByResponseKey.testingUT ?? [], - e2e: metadata.answersByResponseKey.testingE2E ?? [], - } - testingConventionCache.set(normalized, values) - return values -} - -const findConventionValue = (values: string[], target: string): string | null => { - const normalizedTarget = normalizeConventionValue(target) - return values.find((value) => normalizeConventionValue(value) === normalizedTarget) ?? null -} - -const BEHAVE_DEPENDENCIES = ["behave", "behave-django", "behave-webdriver"] - -export const detectPythonTestingSignals = async ( - paths: string[], - pkg: PackageJson | null, - testing: Set, -): Promise => { - const { unit } = await getTestingConventionValues("python") - if (unit.length === 0) { - return - } - - const behaveValue = findConventionValue(unit, "behave") - const unittestValue = findConventionValue(unit, "unittest") - - if (!behaveValue && !unittestValue) { - return - } - - const lowerCasePaths = paths.map((path) => path.toLowerCase()) - - if (behaveValue) { - const hasFeaturesDir = lowerCasePaths.some((path) => path.startsWith("features/") || path.includes("/features/")) - const hasStepsDir = lowerCasePaths.some((path) => path.includes("/steps/")) - const hasEnvironment = lowerCasePaths.some((path) => path.endsWith("/environment.py") || path.endsWith("environment.py")) - const hasDependency = pkg ? dependencyHas(pkg, BEHAVE_DEPENDENCIES) : false - - if (hasDependency || (hasFeaturesDir && (hasStepsDir || hasEnvironment))) { - testing.add(behaveValue) - } - } - - if (unittestValue) { - const hasUnitFiles = lowerCasePaths.some((path) => { - if (!/(^|\/)(tests?|testcases|specs)\//.test(path)) { - return false - } - return /(^|\/)(test_[^/]+|[^/]+_test)\.py$/.test(path) - }) - - if (hasUnitFiles) { - testing.add(unittestValue) - } - } -} - const readPackageJson = async ( owner: string, repo: string, diff --git a/app/layout.tsx b/app/layout.tsx index a5ceef7..8d2ef7b 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,22 +1,11 @@ import type { Metadata } from "next"; import { Suspense } from "react"; import Script from "next/script"; -import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import { ThemeProvider } from "@/components/theme-provider"; import { MixpanelInit } from "@/components/MixpanelInit"; import { SITE_URL } from "@/lib/site-metadata"; -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - const siteUrl = SITE_URL; const siteTitle = "DevContext – AI Coding Guidelines & Repo Analyzer"; const siteDescription = @@ -155,9 +144,7 @@ export default function RootLayout({ }>) { return ( - +