@@ -5,11 +5,14 @@ import type {
55 RepoScanResponse ,
66 RepoScanSummary ,
77 RepoStructureSummary ,
8+ GitHubTreeItem ,
9+ PackageJson ,
810} from "@/types/repo-scan"
911import { buildDependencyAnalysisTasks , hasDependencyDetectionRules } from "@/lib/stack-detection"
1012import type { DependencyAnalysisTask } from "@/lib/stack-detection"
11- import { loadStackQuestionMetadata , normalizeConventionValue } from "@/lib/question-metadata"
1213import { loadStackConventions } from "@/lib/conventions"
14+ import { dependencyHas } from "@/lib/repo-scan/dependency-utils"
15+ import { detectPythonTestingSignals } from "@/lib/repo-scan/python-testing-signals"
1316import { inferStackFromScan } from "@/lib/scan-to-wizard"
1417import { stackQuestion } from "@/lib/wizard-config"
1518
@@ -20,33 +23,6 @@ const JSON_HEADERS = {
2023 Accept : "application/vnd.github+json" ,
2124}
2225
23- interface GitHubTreeItem {
24- path : string
25- type : "blob" | "tree" | string
26- }
27-
28- interface PackageJson {
29- dependencies ?: Record < string , string >
30- devDependencies ?: Record < string , string >
31- peerDependencies ?: Record < string , string >
32- optionalDependencies ?: Record < string , string >
33- engines ?: { node ?: string }
34- workspaces ?: string [ ] | { packages ?: string [ ] }
35- }
36-
37- const dependencyHas = ( pkg : PackageJson , names : string [ ] ) : boolean => {
38- const sources = [
39- pkg . dependencies ,
40- pkg . devDependencies ,
41- pkg . peerDependencies ,
42- pkg . optionalDependencies ,
43- ]
44-
45- return sources . some ( ( source ) =>
46- source ? names . some ( ( name ) => Object . prototype . hasOwnProperty . call ( source , name ) ) : false ,
47- )
48- }
49-
5026const isNullishOrEmpty = ( value : unknown ) : value is null | undefined | "" => value === null || value === undefined || value === ""
5127
5228const extractRateLimitRemaining = ( response : Response ) : number | null => {
@@ -292,79 +268,6 @@ const detectTooling = async (
292268 }
293269}
294270
295- type TestingConventionValues = {
296- unit : string [ ]
297- e2e : string [ ]
298- }
299-
300- const testingConventionCache = new Map < string , TestingConventionValues > ( )
301-
302- const getTestingConventionValues = async ( stackId : string ) : Promise < TestingConventionValues > => {
303- const normalized = stackId . trim ( ) . toLowerCase ( )
304- if ( testingConventionCache . has ( normalized ) ) {
305- return testingConventionCache . get ( normalized ) !
306- }
307-
308- const metadata = await loadStackQuestionMetadata ( normalized )
309- const values : TestingConventionValues = {
310- unit : metadata . answersByResponseKey . testingUT ?? [ ] ,
311- e2e : metadata . answersByResponseKey . testingE2E ?? [ ] ,
312- }
313- testingConventionCache . set ( normalized , values )
314- return values
315- }
316-
317- const findConventionValue = ( values : string [ ] , target : string ) : string | null => {
318- const normalizedTarget = normalizeConventionValue ( target )
319- return values . find ( ( value ) => normalizeConventionValue ( value ) === normalizedTarget ) ?? null
320- }
321-
322- const BEHAVE_DEPENDENCIES = [ "behave" , "behave-django" , "behave-webdriver" ]
323-
324- export const detectPythonTestingSignals = async (
325- paths : string [ ] ,
326- pkg : PackageJson | null ,
327- testing : Set < string > ,
328- ) : Promise < void > => {
329- const { unit } = await getTestingConventionValues ( "python" )
330- if ( unit . length === 0 ) {
331- return
332- }
333-
334- const behaveValue = findConventionValue ( unit , "behave" )
335- const unittestValue = findConventionValue ( unit , "unittest" )
336-
337- if ( ! behaveValue && ! unittestValue ) {
338- return
339- }
340-
341- const lowerCasePaths = paths . map ( ( path ) => path . toLowerCase ( ) )
342-
343- if ( behaveValue ) {
344- const hasFeaturesDir = lowerCasePaths . some ( ( path ) => path . startsWith ( "features/" ) || path . includes ( "/features/" ) )
345- const hasStepsDir = lowerCasePaths . some ( ( path ) => path . includes ( "/steps/" ) )
346- const hasEnvironment = lowerCasePaths . some ( ( path ) => path . endsWith ( "/environment.py" ) || path . endsWith ( "environment.py" ) )
347- const hasDependency = pkg ? dependencyHas ( pkg , BEHAVE_DEPENDENCIES ) : false
348-
349- if ( hasDependency || ( hasFeaturesDir && ( hasStepsDir || hasEnvironment ) ) ) {
350- testing . add ( behaveValue )
351- }
352- }
353-
354- if ( unittestValue ) {
355- const hasUnitFiles = lowerCasePaths . some ( ( path ) => {
356- if ( ! / ( ^ | \/ ) ( t e s t s ? | t e s t c a s e s | s p e c s ) \/ / . test ( path ) ) {
357- return false
358- }
359- return / ( ^ | \/ ) ( t e s t _ [ ^ / ] + | [ ^ / ] + _ t e s t ) \. p y $ / . test ( path )
360- } )
361-
362- if ( hasUnitFiles ) {
363- testing . add ( unittestValue )
364- }
365- }
366- }
367-
368271const readPackageJson = async (
369272 owner : string ,
370273 repo : string ,
0 commit comments