@@ -52,9 +52,33 @@ const GRADLE_SCAN_ROOT_FILES = [
5252
5353// ── Gradle Scanning ──────────────────────────────────────────
5454
55+ /**
56+ * Extracts module paths declared in a `settings.gradle(.kts)` file.
57+ * Handles both Kotlin DSL (`include("mod:sub")`) and Groovy (`include 'mod:sub'`)
58+ * syntaxes, including multiple modules on a single line.
59+ * Colon-separated module names are converted to filesystem paths (`adapters:web` → `adapters/web`).
60+ * @param {string } content - Raw file content of settings.gradle(.kts).
61+ * @returns {string[] } Module directory paths relative to the project root.
62+ */
63+ function parseSettingsGradleModules ( content ) {
64+ const modules = [ ] ;
65+ const includeRe = / i n c l u d e \s * \( ? \s * ( [ ^ ) ] + ) / g;
66+ const quotedRe = / [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / g;
67+ let includeMatch ;
68+ while ( ( includeMatch = includeRe . exec ( content ) ) !== null ) {
69+ const args = includeMatch [ 1 ] ;
70+ let quotedMatch ;
71+ while ( ( quotedMatch = quotedRe . exec ( args ) ) !== null ) {
72+ modules . push ( quotedMatch [ 1 ] . replace ( / : / g, "/" ) ) ;
73+ }
74+ }
75+ return modules ;
76+ }
77+
5578/**
5679 * Builds a list of Gradle build file paths to scan for technology markers.
57- * Includes root-level Gradle files and `build.gradle(.kts)` inside immediate subdirectories.
80+ * Includes root-level Gradle files, `build.gradle(.kts)` inside immediate subdirectories,
81+ * and modules declared in `settings.gradle(.kts)`.
5882 * @param {string } projectDir - Absolute path to the project root.
5983 * @returns {string[] } Candidate file paths.
6084 */
@@ -65,8 +89,17 @@ function gradleLayoutCandidatePaths(projectDir) {
6589 if ( cached ) return cached ;
6690
6791 const candidates = [ ] ;
92+ const seen = new Set ( ) ;
93+
94+ function add ( filePath ) {
95+ if ( ! seen . has ( filePath ) ) {
96+ candidates . push ( filePath ) ;
97+ seen . add ( filePath ) ;
98+ }
99+ }
100+
68101 for ( const f of GRADLE_SCAN_ROOT_FILES ) {
69- candidates . push ( join ( projectDir , f ) ) ;
102+ add ( join ( projectDir , f ) ) ;
70103 }
71104 let entries ;
72105 try {
@@ -77,9 +110,27 @@ function gradleLayoutCandidatePaths(projectDir) {
77110 for ( const e of entries ) {
78111 if ( ! e . isDirectory ( ) || e . name . startsWith ( "." ) || SCAN_SKIP_DIRS . has ( e . name ) ) continue ;
79112 for ( const g of [ "build.gradle.kts" , "build.gradle" ] ) {
80- candidates . push ( join ( projectDir , e . name , g ) ) ;
113+ add ( join ( projectDir , e . name , g ) ) ;
81114 }
82115 }
116+
117+ // Parse settings.gradle(.kts) for declared modules (handles deep nesting)
118+ for ( const settingsFile of [ "settings.gradle.kts" , "settings.gradle" ] ) {
119+ const settingsPath = join ( projectDir , settingsFile ) ;
120+ let content ;
121+ try {
122+ content = readFileSync ( settingsPath , "utf-8" ) ;
123+ } catch {
124+ continue ;
125+ }
126+ for ( const modulePath of parseSettingsGradleModules ( content ) ) {
127+ for ( const g of [ "build.gradle.kts" , "build.gradle" ] ) {
128+ add ( join ( projectDir , modulePath , g ) ) ;
129+ }
130+ }
131+ break ; // only use the first settings file found
132+ }
133+
83134 _gradleCache . set ( projectDir , candidates ) ;
84135 return candidates ;
85136}
0 commit comments