@dailephd/my-dev-kit-lab 0.3.4 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/README.md +12 -287
  2. package/dist/scripts/security/validate.js +210 -70
  3. package/dist/src/audits/codeRot/detectors/architectureDriftDetector.js +5 -2
  4. package/dist/src/audits/codeRot/detectors/securityValidationAssumptionRotDetector.js +5 -2
  5. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/analyzeBackupConfiguration.js +300 -0
  6. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/checkResult.js +41 -0
  7. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/index.js +8 -0
  8. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/manifestEvidence.js +102 -0
  9. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/parseBackupRules.js +110 -0
  10. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/types.js +27 -0
  11. package/dist/src/mobile/android/advancedSecurity/candidateEvidence.js +50 -0
  12. package/dist/src/mobile/android/advancedSecurity/clipboard/analyzeClipboardSecurity.js +166 -0
  13. package/dist/src/mobile/android/advancedSecurity/clipboard/checkResult.js +74 -0
  14. package/dist/src/mobile/android/advancedSecurity/clipboard/collectClipboardEvidence.js +82 -0
  15. package/dist/src/mobile/android/advancedSecurity/clipboard/index.js +1 -0
  16. package/dist/src/mobile/android/advancedSecurity/clipboard/types.js +1 -0
  17. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/checkResult.js +255 -0
  18. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/index.js +1 -0
  19. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/parseLintSarif.js +37 -0
  20. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/parseLintXml.js +36 -0
  21. package/dist/src/mobile/android/advancedSecurity/externalTools/artifacts.js +95 -0
  22. package/dist/src/mobile/android/advancedSecurity/externalTools/boundedOutput.js +33 -0
  23. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/checkResult.js +173 -0
  24. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/command.js +33 -0
  25. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/index.js +1 -0
  26. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/parseDependencyCheckJson.js +133 -0
  27. package/dist/src/mobile/android/advancedSecurity/externalTools/discoverExecutable.js +13 -0
  28. package/dist/src/mobile/android/advancedSecurity/externalTools/index.js +8 -0
  29. package/dist/src/mobile/android/advancedSecurity/externalTools/minimalEnvironment.js +26 -0
  30. package/dist/src/mobile/android/advancedSecurity/externalTools/mutation.js +77 -0
  31. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/checkResult.js +161 -0
  32. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/command.js +25 -0
  33. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/index.js +1 -0
  34. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/parseOsvJson.js +136 -0
  35. package/dist/src/mobile/android/advancedSecurity/externalTools/runBoundedExternalTool.js +51 -0
  36. package/dist/src/mobile/android/advancedSecurity/externalTools/runRequestedAndroidExternalTools.js +54 -0
  37. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/checkResult.js +170 -0
  38. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/command.js +28 -0
  39. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/index.js +2 -0
  40. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/parseSemgrepJson.js +141 -0
  41. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/rules.js +105 -0
  42. package/dist/src/mobile/android/advancedSecurity/externalTools/types.js +107 -0
  43. package/dist/src/mobile/android/advancedSecurity/fileProvider/checkResult.js +188 -0
  44. package/dist/src/mobile/android/advancedSecurity/fileProvider/index.js +1 -0
  45. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/analyzeFirebaseGoogleServices.js +197 -0
  46. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/checkResult.js +77 -0
  47. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/collectFirebaseServiceEvidence.js +193 -0
  48. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/discoverFirebaseArtifacts.js +128 -0
  49. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/index.js +1 -0
  50. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseDatabaseRules.js +55 -0
  51. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseFirebaseConfig.js +75 -0
  52. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseFirestoreStorageRules.js +88 -0
  53. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseGoogleServicesConfig.js +55 -0
  54. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/types.js +6 -0
  55. package/dist/src/mobile/android/advancedSecurity/networkSecurity/analyzeNetworkSecurity.js +394 -0
  56. package/dist/src/mobile/android/advancedSecurity/networkSecurity/checkResult.js +47 -0
  57. package/dist/src/mobile/android/advancedSecurity/networkSecurity/deriveEffectiveNetworkPolicy.js +52 -0
  58. package/dist/src/mobile/android/advancedSecurity/networkSecurity/index.js +14 -0
  59. package/dist/src/mobile/android/advancedSecurity/networkSecurity/manifestEvidence.js +109 -0
  60. package/dist/src/mobile/android/advancedSecurity/networkSecurity/parseNetworkSecurityConfig.js +186 -0
  61. package/dist/src/mobile/android/advancedSecurity/networkSecurity/types.js +1 -0
  62. package/dist/src/mobile/android/advancedSecurity/ordering.js +44 -0
  63. package/dist/src/mobile/android/advancedSecurity/redaction.js +54 -0
  64. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/analyzeReleaseConfiguration.js +221 -0
  65. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/checkResult.js +39 -0
  66. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/index.js +7 -0
  67. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/manifestEvidence.js +19 -0
  68. package/dist/src/mobile/android/advancedSecurity/resourceReference.js +69 -0
  69. package/dist/src/mobile/android/advancedSecurity/resourceResolution.js +118 -0
  70. package/dist/src/mobile/android/advancedSecurity/ruleIds.js +141 -0
  71. package/dist/src/mobile/android/advancedSecurity/secretCandidates/analyzeSecretCandidates.js +116 -0
  72. package/dist/src/mobile/android/advancedSecurity/secretCandidates/checkResult.js +80 -0
  73. package/dist/src/mobile/android/advancedSecurity/secretCandidates/discoverSecretSourceFiles.js +152 -0
  74. package/dist/src/mobile/android/advancedSecurity/secretCandidates/index.js +9 -0
  75. package/dist/src/mobile/android/advancedSecurity/secretCandidates/matchSecretCandidates.js +136 -0
  76. package/dist/src/mobile/android/advancedSecurity/secretCandidates/suppressValue.js +61 -0
  77. package/dist/src/mobile/android/advancedSecurity/secretCandidates/types.js +11 -0
  78. package/dist/src/mobile/android/advancedSecurity/sensitiveCategories.js +34 -0
  79. package/dist/src/mobile/android/advancedSecurity/sensitiveData/classifyDirectExpression.js +99 -0
  80. package/dist/src/mobile/android/advancedSecurity/sensitiveData/classifySensitiveIdentifier.js +111 -0
  81. package/dist/src/mobile/android/advancedSecurity/sensitiveData/index.js +3 -0
  82. package/dist/src/mobile/android/advancedSecurity/sensitiveData/localSourceContext.js +90 -0
  83. package/dist/src/mobile/android/advancedSecurity/sensitiveData/splitArguments.js +59 -0
  84. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/analyzeSensitiveLogging.js +117 -0
  85. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/checkResult.js +74 -0
  86. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/collectLoggingEvidence.js +87 -0
  87. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/index.js +1 -0
  88. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/types.js +1 -0
  89. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/analyzeSensitiveStorage.js +182 -0
  90. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/checkResult.js +74 -0
  91. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/collectStorageEvidence.js +109 -0
  92. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/index.js +1 -0
  93. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/types.js +4 -0
  94. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/analyzeSigningConfiguration.js +270 -0
  95. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/checkResult.js +119 -0
  96. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/discoverKeystoreCandidates.js +55 -0
  97. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/extractSigningConfigurations.js +81 -0
  98. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/index.js +8 -0
  99. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/types.js +20 -0
  100. package/dist/src/mobile/android/advancedSecurity/sourceLocation.js +15 -0
  101. package/dist/src/mobile/android/advancedSecurity/webview/checkResult.js +210 -0
  102. package/dist/src/mobile/android/advancedSecurity/webview/index.js +1 -0
  103. package/dist/src/mobile/android/audit/androidFinding.js +68 -0
  104. package/dist/src/mobile/android/audit/checkResultBuilder.js +97 -0
  105. package/dist/src/mobile/android/audit/deepLinkAudit.js +183 -0
  106. package/dist/src/mobile/android/audit/exportedComponentAudit.js +183 -0
  107. package/dist/src/mobile/android/audit/intentFilterAudit.js +128 -0
  108. package/dist/src/mobile/android/audit/permissionAudit.js +142 -0
  109. package/dist/src/mobile/android/detect/androidTargetDetection.js +18 -0
  110. package/dist/src/mobile/android/detect/buildFileEvidence.js +36 -0
  111. package/dist/src/mobile/android/detect/classify.js +150 -0
  112. package/dist/src/mobile/android/detect/detectAndroidProject.js +252 -0
  113. package/dist/src/mobile/android/detect/settingsModules.js +60 -0
  114. package/dist/src/mobile/android/detect/sourceEvidence.js +92 -0
  115. package/dist/src/mobile/android/detect/traversal.js +79 -0
  116. package/dist/src/mobile/android/detect/versionCatalogEvidence.js +37 -0
  117. package/dist/src/mobile/android/detection.js +28 -0
  118. package/dist/src/mobile/android/gradle/gradleMetadataCheckResult.js +120 -0
  119. package/dist/src/mobile/android/gradle/moduleMetadataExtractor.js +243 -0
  120. package/dist/src/mobile/android/gradle/pluginVersionExtractor.js +40 -0
  121. package/dist/src/mobile/android/gradle/readAndroidGradleMetadata.js +210 -0
  122. package/dist/src/mobile/android/gradle/releaseMetadataSummary.js +64 -0
  123. package/dist/src/mobile/android/gradle/settingsMetadata.js +20 -0
  124. package/dist/src/mobile/android/gradle/types.js +1 -0
  125. package/dist/src/mobile/android/gradle/validate/executor.js +9 -0
  126. package/dist/src/mobile/android/gradle/validate/operationCheckResult.js +99 -0
  127. package/dist/src/mobile/android/gradle/validate/operations.js +68 -0
  128. package/dist/src/mobile/android/gradle/validate/planner.js +49 -0
  129. package/dist/src/mobile/android/gradle/validate/runOptionalGradleValidation.js +113 -0
  130. package/dist/src/mobile/android/gradle/validate/targetMutation.js +45 -0
  131. package/dist/src/mobile/android/gradle/validate/taskListParser.js +24 -0
  132. package/dist/src/mobile/android/gradle/versionCatalogMetadata.js +94 -0
  133. package/dist/src/mobile/android/gradle/wrapperMetadata.js +32 -0
  134. package/dist/src/mobile/android/manifest/parseAndroidManifest.js +364 -0
  135. package/dist/src/mobile/android/manifest/types.js +29 -0
  136. package/dist/src/mobile/android/manifest/xml/parseXml.js +329 -0
  137. package/dist/src/mobile/android/profile.js +29 -0
  138. package/dist/src/mobile/android/report/model.js +135 -0
  139. package/dist/src/mobile/android/report/renderAndroidReport.js +286 -0
  140. package/dist/src/mobile/android/report/writeAndroidReportFiles.js +23 -0
  141. package/dist/src/mobile/android/targetMetadata.js +27 -0
  142. package/dist/src/mobile/android/validate/androidVerdict.js +248 -0
  143. package/dist/src/mobile/android/validate/detectionAndImmutabilityCheckResults.js +177 -0
  144. package/dist/src/mobile/android/validate/playReadinessChecklist.js +145 -0
  145. package/dist/src/mobile/android/validate/validateAndroidTarget.js +315 -0
  146. package/dist/src/mobile/android/validation/checkResult.js +58 -0
  147. package/dist/src/mobile/android/validation/result.js +25 -0
  148. package/dist/src/mobile/index.js +42 -0
  149. package/dist/src/mobile/types.js +27 -0
  150. package/dist/src/securityValidation/attackScenarios/attackProfile.js +2 -0
  151. package/dist/src/securityValidation/attackScenarios/profiles/androidProfile.js +20 -0
  152. package/dist/src/securityValidation/commandRunner.js +10 -1
  153. package/dist/src/securityValidation/validate/cliOptions.js +92 -2
  154. package/package.json +2 -1
  155. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/debug/res/xml/ambiguous_backup_rules.xml +4 -0
  156. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/ambiguous_backup_rules.xml +4 -0
  157. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/backup_rules.xml +5 -0
  158. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/broad_backup_rules.xml +4 -0
  159. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/data_extraction_rules.xml +10 -0
  160. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/unsupported_domain_rules.xml +4 -0
  161. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-b/src/main/res/xml/malformed_backup_rules.xml +4 -0
  162. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-b/src/main/res/xml/unsupported_root.xml +4 -0
  163. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/debug/res/xml/ambiguous_config.xml +4 -0
  164. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/ambiguous_config.xml +4 -0
  165. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/base_cleartext_false.xml +8 -0
  166. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/broad_and_duplicate_domains.xml +8 -0
  167. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/empty_domain.xml +6 -0
  168. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/malformed_pin.xml +11 -0
  169. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/network_security_config.xml +26 -0
  170. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-b/src/main/res/xml/malformed_network_security_config.xml +6 -0
  171. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-b/src/main/res/xml/unsupported_root.xml +4 -0
  172. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-dynamic.gradle +7 -0
  173. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-false.gradle +9 -0
  174. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-true.gradle +13 -0
  175. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/kotlin-release-debuggable-false.gradle.kts +7 -0
  176. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/kotlin-release-debuggable-true.gradle.kts +10 -0
  177. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/missing-release-build-type.gradle.kts +7 -0
  178. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/debug/res/xml/network_security_config.xml +8 -0
  179. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/main/res/xml/backup_rules.xml +4 -0
  180. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/main/res/xml/network_security_config.xml +8 -0
  181. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-b/src/main/res/xml/malformed_network_security_config.xml +7 -0
  182. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/SampleActivity.java +8 -0
  183. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/SampleActivity.kt +9 -0
  184. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/build.gradle +11 -0
  185. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/build.gradle.kts +11 -0
  186. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/google-services.json +16 -0
  187. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/local.properties +3 -0
  188. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates-bounds/module/binary.properties +0 -0
  189. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates-bounds/module/oversized.json +1 -0
  190. package/tests/fixtures/android/advanced-security-fixtures/signing-configuration/app/fake-release-keystore.jks +1 -0
  191. package/tests/fixtures/android/advanced-security-fixtures/signing-configuration/build/generated-fake.keystore +1 -0
  192. package/tests/fixtures/android/compose-app/app/build.gradle.kts +21 -0
  193. package/tests/fixtures/android/compose-app/app/src/main/AndroidManifest.xml +22 -0
  194. package/tests/fixtures/android/compose-app/app/src/main/java/com/example/composeapp/MainActivity.kt +11 -0
  195. package/tests/fixtures/android/compose-app/build.gradle.kts +4 -0
  196. package/tests/fixtures/android/compose-app/gradle/wrapper/gradle-wrapper.properties +4 -0
  197. package/tests/fixtures/android/compose-app/settings.gradle.kts +2 -0
  198. package/tests/fixtures/android/gradle-metadata-fixtures/catalog-only-plugin/build.gradle.kts +3 -0
  199. package/tests/fixtures/android/gradle-metadata-fixtures/catalog-only-plugin/gradle/libs.versions.toml +5 -0
  200. package/tests/fixtures/android/gradle-metadata-fixtures/comment-safety.gradle.kts +21 -0
  201. package/tests/fixtures/android/gradle-metadata-fixtures/dynamic-values.gradle.kts +17 -0
  202. package/tests/fixtures/android/gradle-metadata-fixtures/groovy-literal.gradle +23 -0
  203. package/tests/fixtures/android/gradle-metadata-fixtures/wrapper-malformed.properties +4 -0
  204. package/tests/fixtures/android/gradle-metadata-fixtures/wrapper-missing-url.properties +3 -0
  205. package/tests/fixtures/android/gradle-metadata-fixtures/wrapper-with-checksum.properties +5 -0
  206. package/tests/fixtures/android/java-app/app/build.gradle +16 -0
  207. package/tests/fixtures/android/java-app/app/src/main/AndroidManifest.xml +19 -0
  208. package/tests/fixtures/android/java-app/app/src/main/java/com/example/javaapp/MainActivity.java +4 -0
  209. package/tests/fixtures/android/java-app/build.gradle +3 -0
  210. package/tests/fixtures/android/java-app/gradle/wrapper/gradle-wrapper.properties +4 -0
  211. package/tests/fixtures/android/java-app/settings.gradle +2 -0
  212. package/tests/fixtures/android/library/build.gradle.kts +3 -0
  213. package/tests/fixtures/android/library/gradle/wrapper/gradle-wrapper.properties +4 -0
  214. package/tests/fixtures/android/library/mylibrary/build.gradle.kts +13 -0
  215. package/tests/fixtures/android/library/mylibrary/src/main/AndroidManifest.xml +2 -0
  216. package/tests/fixtures/android/library/settings.gradle.kts +2 -0
  217. package/tests/fixtures/android/manifest-audit-fixtures/incomplete-intent-filter.xml +14 -0
  218. package/tests/fixtures/android/manifest-audit-fixtures/malformed-exported.xml +10 -0
  219. package/tests/fixtures/android/manifest-audit-fixtures/malformed.xml +5 -0
  220. package/tests/fixtures/android/manifest-audit-fixtures/missing-application.xml +7 -0
  221. package/tests/fixtures/android/manifest-audit-fixtures/missing-component-name.xml +11 -0
  222. package/tests/fixtures/android/manifest-audit-fixtures/permission-coverage.xml +29 -0
  223. package/tests/fixtures/android/manifest-audit-fixtures/risky-components.xml +62 -0
  224. package/tests/fixtures/android/manifest-audit-fixtures/risky-deep-links.xml +45 -0
  225. package/tests/fixtures/android/manifest-audit-fixtures/unresolved-placeholder.xml +14 -0
  226. package/tests/fixtures/android/manifest-audit-fixtures/unusual-namespace-prefix.xml +11 -0
  227. package/tests/fixtures/android/multi-module/app/build.gradle.kts +22 -0
  228. package/tests/fixtures/android/multi-module/app/src/androidTest/java/com/example/multimodule/app/ExampleInstrumentedTest.kt +3 -0
  229. package/tests/fixtures/android/multi-module/app/src/main/AndroidManifest.xml +11 -0
  230. package/tests/fixtures/android/multi-module/app/src/test/java/com/example/multimodule/app/ExampleUnitTest.kt +3 -0
  231. package/tests/fixtures/android/multi-module/build.gradle.kts +5 -0
  232. package/tests/fixtures/android/multi-module/core/build.gradle.kts +14 -0
  233. package/tests/fixtures/android/multi-module/core/src/main/AndroidManifest.xml +2 -0
  234. package/tests/fixtures/android/multi-module/feature-login/build.gradle.kts +18 -0
  235. package/tests/fixtures/android/multi-module/feature-login/src/main/AndroidManifest.xml +2 -0
  236. package/tests/fixtures/android/multi-module/gradle/libs.versions.toml +8 -0
  237. package/tests/fixtures/android/multi-module/gradle/wrapper/gradle-wrapper.properties +4 -0
  238. package/tests/fixtures/android/multi-module/settings.gradle.kts +4 -0
  239. package/tests/fixtures/android/non-android-gradle/build.gradle.kts +3 -0
  240. package/tests/fixtures/android/non-android-gradle/core/build.gradle.kts +3 -0
  241. package/tests/fixtures/android/non-android-gradle/core/src/main/java/com/example/core/Core.java +4 -0
  242. package/tests/fixtures/android/non-android-gradle/gradle/wrapper/gradle-wrapper.properties +4 -0
  243. package/tests/fixtures/android/non-android-gradle/settings.gradle.kts +2 -0
  244. package/tests/fixtures/android/partial/app/build.gradle.kts +7 -0
  245. package/tests/fixtures/android/partial/settings.gradle.kts +2 -0
  246. package/tests/fixtures/android/xml-view-app/app/build.gradle.kts +17 -0
  247. package/tests/fixtures/android/xml-view-app/app/src/main/AndroidManifest.xml +31 -0
  248. package/tests/fixtures/android/xml-view-app/app/src/main/kotlin/com/example/xmlviewapp/MainActivity.kt +7 -0
  249. package/tests/fixtures/android/xml-view-app/app/src/main/res/layout/activity_main.xml +4 -0
  250. package/tests/fixtures/android/xml-view-app/build.gradle.kts +4 -0
  251. package/tests/fixtures/android/xml-view-app/gradle/wrapper/gradle-wrapper.properties +4 -0
  252. package/tests/fixtures/android/xml-view-app/settings.gradle.kts +2 -0
@@ -0,0 +1,37 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.0 Batch 2 — bounded gradle/libs.versions.toml plugin-alias evidence.
3
+ //
4
+ // This is deliberately not a general TOML parser: it only line-scans the
5
+ // `[plugins]`/`[libraries]` sections for aliases whose declared id/module
6
+ // statically mentions an Android/Kotlin-Android/Compose plugin or library.
7
+ // Anything else in the catalog (bundles, rich versions, references) is left
8
+ // unresolved, matching the "bounded evidence only" requirement for this
9
+ // batch — a full catalog resolver belongs to a later batch if ever needed.
10
+ // ---------------------------------------------------------------------------
11
+ const ALIAS_LINE_PATTERN = /^([A-Za-z0-9_-]+)\s*=\s*(\{.*\}|".*")\s*$/;
12
+ export function extractVersionCatalogEvidence(text) {
13
+ const androidPluginAliasEvidence = [];
14
+ const kotlinAndroidPluginAliasEvidence = [];
15
+ const composeAliasEvidence = [];
16
+ for (const rawLine of text.split(/\r?\n/)) {
17
+ const line = rawLine.trim();
18
+ const match = line.match(ALIAS_LINE_PATTERN);
19
+ if (!match)
20
+ continue;
21
+ const [, alias, body] = match;
22
+ if (body.includes("com.android.application") || body.includes("com.android.library")) {
23
+ androidPluginAliasEvidence.push(alias);
24
+ }
25
+ if (body.includes("org.jetbrains.kotlin.android")) {
26
+ kotlinAndroidPluginAliasEvidence.push(alias);
27
+ }
28
+ if (/compose/i.test(body) || /androidx\.compose/i.test(body)) {
29
+ composeAliasEvidence.push(alias);
30
+ }
31
+ }
32
+ return {
33
+ androidPluginAliasEvidence: androidPluginAliasEvidence.sort((a, b) => a.localeCompare(b)),
34
+ kotlinAndroidPluginAliasEvidence: kotlinAndroidPluginAliasEvidence.sort((a, b) => a.localeCompare(b)),
35
+ composeAliasEvidence: composeAliasEvidence.sort((a, b) => a.localeCompare(b)),
36
+ };
37
+ }
@@ -0,0 +1,28 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.0 Batch 1 — Android detection result contract.
3
+ // v0.4.0 Batch 2 — narrow inherited-contract correction: ANDROID_PROJECT_KINDS
4
+ // widened (additive) from {application, library, unknown} to also cover
5
+ // project-level outcomes ("multi-module", "mixed", "partial", "non-android")
6
+ // required by Batch 2 classification (see agents.txt Batch 2 section 7.8).
7
+ // The original three values are preserved unchanged and remain the only
8
+ // values a single AndroidModuleDetection.kind can take in practice, so no
9
+ // existing consumer or test is broken by the widening. AndroidModuleDetection
10
+ // also gained optional confidence/evidence fields so per-module
11
+ // classification results (Batch 2 section 7.7) can be evidence-backed
12
+ // without a second module-result type.
13
+ //
14
+ // This is the normalized shape a future detection batch will populate. This
15
+ // batch defines the contract only; no detection algorithm exists yet.
16
+ // "uncertain"/"unknown" outcomes must never be normalized into a positive
17
+ // detection — callers must check `detected` explicitly.
18
+ // ---------------------------------------------------------------------------
19
+ export const ANDROID_PROJECT_KINDS = [
20
+ "application",
21
+ "library",
22
+ "unknown",
23
+ "multi-module",
24
+ "mixed",
25
+ "partial",
26
+ "non-android",
27
+ ];
28
+ export const ANDROID_UI_TOOLKITS = ["compose", "xml-view", "mixed", "uncertain"];
@@ -0,0 +1,120 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.0 Batch 4 — static Gradle metadata check-result construction
3
+ // (agents.txt Batch 4 section 7.16).
4
+ //
5
+ // Status policy:
6
+ // - non-Android target -> "unsupported" (optional)
7
+ // - Android detected but no module build -> "inconclusive" (required)
8
+ // file could be read
9
+ // - Android detected but no metadata field -> "inconclusive" (required)
10
+ // could be resolved from any module
11
+ // - usable evidence resolved -> "passed"
12
+ // Unresolved dynamic values and metadata conflicts are represented as
13
+ // warnings/evidence — they never fail this check on their own, matching the
14
+ // "validation evidence, not automatic vulnerability" framing used throughout
15
+ // this batch. There is no reachable "malformed"/"error" path for plain-text
16
+ // Gradle files the way there is for XML: readAndroidGradleMetadata treats an
17
+ // unreadable file the same as an absent one, and all paths inspected here
18
+ // come from the already target-contained Batch 2 detection result.
19
+ // ---------------------------------------------------------------------------
20
+ const CHECK_ID = "android-gradle-metadata";
21
+ export function buildAndroidGradleMetadataCheckResult(detection, gradle) {
22
+ const sourcePaths = [...gradle.settingsFiles, ...gradle.rootBuildFiles, ...gradle.moduleBuildFiles].sort((a, b) => a.localeCompare(b));
23
+ const evidence = [];
24
+ if (gradle.wrapper.versionEvidence)
25
+ evidence.push(`Gradle wrapper version: ${gradle.wrapper.versionEvidence}`);
26
+ if (gradle.pluginEvidence.androidGradlePluginVersion)
27
+ evidence.push(`Android Gradle Plugin version: ${gradle.pluginEvidence.androidGradlePluginVersion}`);
28
+ if (gradle.pluginEvidence.kotlinAndroidPluginVersion)
29
+ evidence.push(`Kotlin Android plugin version: ${gradle.pluginEvidence.kotlinAndroidPluginVersion}`);
30
+ for (const module of gradle.modules) {
31
+ if (module.namespace || module.applicationId) {
32
+ evidence.push(`${module.path}: namespace=${module.namespace ?? "(none)"} applicationId=${module.applicationId ?? "(none)"}`);
33
+ }
34
+ }
35
+ const warnings = [...gradle.parseWarnings];
36
+ if (gradle.conflicts && gradle.conflicts.length > 0) {
37
+ warnings.push(...gradle.conflicts.map((c) => c.note));
38
+ }
39
+ if (detection.projectKind === "non-android") {
40
+ return {
41
+ id: CHECK_ID,
42
+ category: "android-gradle",
43
+ title: "Android Gradle metadata extraction",
44
+ status: "unsupported",
45
+ requirementLevel: "optional",
46
+ ran: false,
47
+ skipped: true,
48
+ skipInfo: {
49
+ checkId: CHECK_ID,
50
+ reason: "Target was not detected as an Android project.",
51
+ requirementLevel: "optional",
52
+ missingCapability: "android-detection",
53
+ verdictImpact: "does not apply to a non-Android target",
54
+ recommendedNextAction: "Re-run against an Android Gradle project, or verify project detection.",
55
+ },
56
+ evidence: [],
57
+ findings: [],
58
+ warnings: [],
59
+ errors: [],
60
+ sourcePaths: [],
61
+ confidence: "unknown",
62
+ environmentRequirements: [],
63
+ };
64
+ }
65
+ const hasModuleBuildFile = gradle.moduleBuildFiles.length > 0;
66
+ const hasAnyResolvedModuleMetadata = gradle.modules.some((m) => m.namespace || m.applicationId || m.minSdk || m.targetSdk || m.compileSdk || m.versionName || m.versionCode);
67
+ if (!hasModuleBuildFile) {
68
+ return {
69
+ id: CHECK_ID,
70
+ category: "android-gradle",
71
+ title: "Android Gradle metadata extraction",
72
+ status: "inconclusive",
73
+ requirementLevel: "required",
74
+ ran: true,
75
+ skipped: false,
76
+ evidence,
77
+ findings: [],
78
+ warnings: [...warnings, "No module build.gradle(.kts) file could be read for metadata extraction."],
79
+ errors: [],
80
+ sourcePaths,
81
+ confidence: "unknown",
82
+ environmentRequirements: [],
83
+ };
84
+ }
85
+ if (!hasAnyResolvedModuleMetadata) {
86
+ return {
87
+ id: CHECK_ID,
88
+ category: "android-gradle",
89
+ title: "Android Gradle metadata extraction",
90
+ status: "inconclusive",
91
+ requirementLevel: "required",
92
+ ran: true,
93
+ skipped: false,
94
+ evidence,
95
+ findings: [],
96
+ warnings: [...warnings, "Module build file(s) were read but no static metadata field could be resolved."],
97
+ errors: [],
98
+ sourcePaths,
99
+ confidence: gradle.metadataConfidence,
100
+ environmentRequirements: [],
101
+ };
102
+ }
103
+ const confidence = gradle.metadataConfidence;
104
+ return {
105
+ id: CHECK_ID,
106
+ category: "android-gradle",
107
+ title: "Android Gradle metadata extraction",
108
+ status: "passed",
109
+ requirementLevel: "required",
110
+ ran: true,
111
+ skipped: false,
112
+ evidence,
113
+ findings: [],
114
+ warnings,
115
+ errors: [],
116
+ sourcePaths,
117
+ confidence,
118
+ environmentRequirements: [],
119
+ };
120
+ }
@@ -0,0 +1,243 @@
1
+ import { extractBuildFileEvidence } from "../detect/buildFileEvidence.js";
2
+ // ---------------------------------------------------------------------------
3
+ // v0.4.0 Batch 4 — bounded literal metadata extraction from a single module
4
+ // build.gradle(.kts) file's text.
5
+ //
6
+ // Reuses Batch 2's extractBuildFileEvidence for plugin/Compose boolean
7
+ // evidence (already covers namespace/applicationId at a basic level) and
8
+ // adds the additional literal fields Batch 4 needs (version/SDK fields,
9
+ // build-type names) plus raw/unresolved-expression preservation for every
10
+ // field, using the same generic "try a literal, else capture a bounded raw
11
+ // excerpt" strategy for each key. Comments are stripped first so a
12
+ // commented-out assignment never becomes active metadata (ANDROID-B4-05).
13
+ // ---------------------------------------------------------------------------
14
+ // v0.4.1 Batch 4 — exported (was module-private) so the new signing-
15
+ // configuration module can reuse the exact same comment-stripping/literal-
16
+ // extraction/brace-matching technique instead of a second implementation.
17
+ export function stripComments(text) {
18
+ // Replaces comment characters with spaces (not empty string) so any
19
+ // surviving line/column-independent offsets are not shifted — this batch
20
+ // does not track per-field locations, but keeping length stable is cheap
21
+ // and avoids accidentally joining tokens across a removed comment.
22
+ return text
23
+ .replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, " "))
24
+ .replace(/\/\/[^\n]*/g, (match) => " ".repeat(match.length));
25
+ }
26
+ const STRING_OR_INT = `(?:'([^']*)'|"([^"]*)"|(-?\\d+))`;
27
+ export function extractLiteral(text, keyAlternatives) {
28
+ const keyPattern = keyAlternatives.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
29
+ // Anchored to the start of a line (allowing leading indentation) so a key
30
+ // name that merely appears as a word inside an unrelated string literal —
31
+ // e.g. `val note = "the applicationId field below is the real one"` — is
32
+ // never mistaken for a real assignment (ANDROID-B4-05). Genuine Gradle
33
+ // property assignments always begin a statement/line.
34
+ const literalPattern = new RegExp(`^[ \\t]*(?:${keyPattern})\\b\\s*[=]?\\s*${STRING_OR_INT}`, "m");
35
+ const literalMatch = text.match(literalPattern);
36
+ if (literalMatch) {
37
+ const value = literalMatch[1] ?? literalMatch[2] ?? literalMatch[3];
38
+ return { value };
39
+ }
40
+ const rawPattern = new RegExp(`^[ \\t]*(?:${keyPattern})\\b\\s*[=]?\\s*([^\\n]{1,120})`, "m");
41
+ const rawMatch = text.match(rawPattern);
42
+ if (rawMatch) {
43
+ const raw = rawMatch[1].trim();
44
+ // A raw excerpt that is itself just a bare literal (already handled
45
+ // above) or empty is not meaningfully "dynamic" — only report genuinely
46
+ // unresolved expressions.
47
+ if (raw.length > 0)
48
+ return { raw };
49
+ }
50
+ return {};
51
+ }
52
+ // Returns the inner text of the first `<blockKeyword> { ... }` block found,
53
+ // bounded by brace-depth matching (not a full parser). Shared by
54
+ // findNamedBlockEntries (names only) and extractNamedSubBlocks (v0.4.1
55
+ // Batch 3 — names plus each entry's own block content).
56
+ export function findBlockBody(text, blockKeyword) {
57
+ const blockStart = text.search(new RegExp(`\\b${blockKeyword}\\s*\\{`));
58
+ if (blockStart === -1)
59
+ return undefined;
60
+ const openBraceIndex = text.indexOf("{", blockStart);
61
+ return sliceBlockAt(text, openBraceIndex);
62
+ }
63
+ // Given the index of an opening `{`, returns the text between it and its
64
+ // matching `}` (brace-depth bounded), or undefined if unterminated.
65
+ export function sliceBlockAt(text, openBraceIndex) {
66
+ if (text[openBraceIndex] !== "{")
67
+ return undefined;
68
+ let depth = 0;
69
+ for (let i = openBraceIndex; i < text.length; i++) {
70
+ if (text[i] === "{")
71
+ depth += 1;
72
+ else if (text[i] === "}") {
73
+ depth -= 1;
74
+ if (depth === 0)
75
+ return text.slice(openBraceIndex + 1, i);
76
+ }
77
+ }
78
+ return undefined;
79
+ }
80
+ // Finds the top-level named entries inside a `<blockKeyword> { ... }` block
81
+ // (e.g. `buildTypes { debug { ... } release { ... } }` or
82
+ // `sourceSets { main { ... } androidTest { ... } }`), supporting both the
83
+ // bare-identifier Groovy/Kotlin-DSL form and the Kotlin DSL
84
+ // getByName/create/named(...) form. Bounded by brace-depth matching, not a
85
+ // full parser — nested unrelated blocks inside are not descended into for
86
+ // name extraction beyond the immediate children.
87
+ function findNamedBlockEntries(text, blockKeyword) {
88
+ const block = findBlockBody(text, blockKeyword);
89
+ if (block === undefined)
90
+ return [];
91
+ const names = new Set();
92
+ for (const match of block.matchAll(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\{/g)) {
93
+ names.add(match[1]);
94
+ }
95
+ for (const match of block.matchAll(/\b(?:getByName|create|named)\(\s*["']([\w-]+)["']/g)) {
96
+ names.add(match[1]);
97
+ }
98
+ return [...names].sort((a, b) => a.localeCompare(b));
99
+ }
100
+ // v0.4.1 Batch 3 — like findNamedBlockEntries, but also returns each named
101
+ // entry's own block content so per-build-type literal fields (debuggable,
102
+ // minifyEnabled, etc.) can be extracted. Reuses the exact same bounded
103
+ // brace-matching technique and the same two name-pattern forms — not a
104
+ // second parser, just returning more of what was already being scanned.
105
+ export function extractNamedSubBlocks(text, blockKeyword) {
106
+ const block = findBlockBody(text, blockKeyword);
107
+ if (block === undefined)
108
+ return [];
109
+ const results = [];
110
+ const seen = new Set();
111
+ for (const match of block.matchAll(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\{/g)) {
112
+ const name = match[1];
113
+ if (seen.has(name))
114
+ continue;
115
+ const openBraceIndex = match.index + match[0].length - 1;
116
+ const content = sliceBlockAt(block, openBraceIndex);
117
+ if (content !== undefined) {
118
+ results.push({ name, content });
119
+ seen.add(name);
120
+ }
121
+ }
122
+ for (const match of block.matchAll(/\b(?:getByName|create|named)\(\s*["']([\w-]+)["']\s*\)\s*\{/g)) {
123
+ const name = match[1];
124
+ if (seen.has(name))
125
+ continue;
126
+ const openBraceIndex = match.index + match[0].length - 1;
127
+ const content = sliceBlockAt(block, openBraceIndex);
128
+ if (content !== undefined) {
129
+ results.push({ name, content });
130
+ seen.add(name);
131
+ }
132
+ }
133
+ return results.sort((a, b) => a.name.localeCompare(b.name));
134
+ }
135
+ const BOOLEAN_TOKEN = `(true|false)`;
136
+ // v0.4.1 Batch 3 — boolean-literal counterpart to extractLiteral (which only
137
+ // matches quoted-string/integer literals — Gradle boolean assignments like
138
+ // `debuggable true` or `isMinifyEnabled = true` are bare tokens, not quoted).
139
+ // Same anchoring/fallback strategy as extractLiteral for consistency.
140
+ export function extractBooleanLiteral(text, keyAlternatives) {
141
+ const keyPattern = keyAlternatives.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
142
+ const literalPattern = new RegExp(`^[ \\t]*(?:${keyPattern})\\b\\s*[=(]?\\s*${BOOLEAN_TOKEN}\\)?`, "m");
143
+ const literalMatch = text.match(literalPattern);
144
+ if (literalMatch) {
145
+ return { value: literalMatch[1] === "true" };
146
+ }
147
+ const rawPattern = new RegExp(`^[ \\t]*(?:${keyPattern})\\b\\s*[=(]?\\s*([^\\n]{1,120})`, "m");
148
+ const rawMatch = text.match(rawPattern);
149
+ if (rawMatch) {
150
+ const raw = rawMatch[1].trim();
151
+ if (raw.length > 0)
152
+ return { raw };
153
+ }
154
+ return {};
155
+ }
156
+ function booleanLiteralState(result) {
157
+ if (result.value !== undefined)
158
+ return result.value ? "literal-true" : "literal-false";
159
+ if (result.raw !== undefined)
160
+ return "dynamic";
161
+ return "missing";
162
+ }
163
+ // v0.4.1 Batch 3 — builds bounded literal evidence for one named build-type
164
+ // block. Never evaluates Gradle; signingConfig/matchingFallbacks/initWith
165
+ // are always reference expressions, so only a bounded raw excerpt is kept.
166
+ function buildBuildTypeInfo(name, content) {
167
+ const debuggable = extractBooleanLiteral(content, ["debuggable", "isDebuggable"]);
168
+ const minifyEnabled = extractBooleanLiteral(content, ["minifyEnabled", "isMinifyEnabled"]);
169
+ const shrinkResources = extractBooleanLiteral(content, ["shrinkResources", "isShrinkResources"]);
170
+ const signingConfig = extractLiteral(content, ["signingConfig"]);
171
+ const matchingFallbacks = extractLiteral(content, ["matchingFallbacks"]);
172
+ const initWith = extractLiteral(content, ["initWith"]);
173
+ return {
174
+ name,
175
+ debuggable: debuggable.value,
176
+ debuggableRaw: debuggable.raw,
177
+ debuggableState: booleanLiteralState(debuggable),
178
+ minifyEnabled: minifyEnabled.value,
179
+ minifyEnabledRaw: minifyEnabled.raw,
180
+ minifyEnabledState: booleanLiteralState(minifyEnabled),
181
+ shrinkResources: shrinkResources.value,
182
+ shrinkResourcesRaw: shrinkResources.raw,
183
+ shrinkResourcesState: booleanLiteralState(shrinkResources),
184
+ signingConfigRef: signingConfig.value ?? signingConfig.raw,
185
+ matchingFallbacksRaw: matchingFallbacks.value ?? matchingFallbacks.raw,
186
+ initWithRaw: initWith.value ?? initWith.raw,
187
+ };
188
+ }
189
+ // Extracts bounded static metadata for one module's build file text. `path`
190
+ // is intentionally omitted from the returned info — the caller (the
191
+ // aggregator, which already knows the module path from detection) attaches
192
+ // it, keeping this function pure with respect to filesystem/module identity.
193
+ export function extractModuleGradleMetadata(rawText) {
194
+ const text = stripComments(rawText);
195
+ const evidence = extractBuildFileEvidence(text);
196
+ const unsupportedExpressions = [];
197
+ const namespace = extractLiteral(text, ["namespace"]);
198
+ const applicationId = extractLiteral(text, ["applicationId"]);
199
+ const versionCode = extractLiteral(text, ["versionCode"]);
200
+ const versionName = extractLiteral(text, ["versionName"]);
201
+ const minSdk = extractLiteral(text, ["minSdk", "minSdkVersion"]);
202
+ const targetSdk = extractLiteral(text, ["targetSdk", "targetSdkVersion"]);
203
+ const compileSdk = extractLiteral(text, ["compileSdk", "compileSdkVersion"]);
204
+ for (const [field, result] of [
205
+ ["namespace", namespace],
206
+ ["applicationId", applicationId],
207
+ ["versionCode", versionCode],
208
+ ["versionName", versionName],
209
+ ["minSdk", minSdk],
210
+ ["targetSdk", targetSdk],
211
+ ["compileSdk", compileSdk],
212
+ ]) {
213
+ if (result.raw) {
214
+ unsupportedExpressions.push(`${field}: ${result.raw}`);
215
+ }
216
+ }
217
+ return {
218
+ info: {
219
+ isApplication: evidence.androidApplicationPlugin || undefined,
220
+ isLibrary: evidence.androidLibraryPlugin || undefined,
221
+ namespace: namespace.value ?? evidence.namespace,
222
+ namespaceRaw: namespace.raw,
223
+ applicationId: applicationId.value ?? evidence.applicationId,
224
+ applicationIdRaw: applicationId.raw,
225
+ versionCode: versionCode.value,
226
+ versionCodeRaw: versionCode.raw,
227
+ versionName: versionName.value,
228
+ versionNameRaw: versionName.raw,
229
+ minSdk: minSdk.value,
230
+ minSdkRaw: minSdk.raw,
231
+ targetSdk: targetSdk.value,
232
+ targetSdkRaw: targetSdk.raw,
233
+ compileSdk: compileSdk.value,
234
+ compileSdkRaw: compileSdk.raw,
235
+ buildTypes: findNamedBlockEntries(text, "buildTypes"),
236
+ buildTypeDetails: extractNamedSubBlocks(text, "buildTypes").map(({ name, content }) => buildBuildTypeInfo(name, content)),
237
+ composeEnabled: evidence.composeBuildFeatureEvidence || evidence.composeDependencyEvidence || undefined,
238
+ sourceSetEvidence: findNamedBlockEntries(text, "sourceSets").map((name) => `declared sourceSet: ${name}`),
239
+ testSourceSetEvidence: [],
240
+ unsupportedExpressions,
241
+ },
242
+ };
243
+ }
@@ -0,0 +1,40 @@
1
+ import { accessorSuffixToCatalogAlias, resolveCatalogVersion } from "./versionCatalogMetadata.js";
2
+ function extractDirectIdVersion(text, pluginId) {
3
+ const escaped = pluginId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4
+ const pattern = new RegExp(`id\\s*\\(?\\s*['"]${escaped}['"]\\s*\\)?\\s*version\\s*['"]([^'"]+)['"]`);
5
+ return text.match(pattern)?.[1];
6
+ }
7
+ function extractClasspathVersion(text, groupArtifact) {
8
+ const escaped = groupArtifact.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9
+ const pattern = new RegExp(`classpath\\s*['"]${escaped}:([^'"]+)['"]`);
10
+ return text.match(pattern)?.[1];
11
+ }
12
+ function extractCatalogAliasVersion(text, pluginId, catalog) {
13
+ if (!catalog)
14
+ return undefined;
15
+ for (const match of text.matchAll(/alias\(\s*libs\.plugins\.([\w.]+)\s*\)/g)) {
16
+ const alias = accessorSuffixToCatalogAlias(match[1]);
17
+ const entry = catalog.plugins[alias];
18
+ if (entry?.id === pluginId) {
19
+ return resolveCatalogVersion(catalog, entry);
20
+ }
21
+ }
22
+ return undefined;
23
+ }
24
+ // Resolves a plugin's version from root/module build-file text plus an
25
+ // optional parsed version catalog. `classpathGroupArtifact` is only relevant
26
+ // for the Android Gradle Plugin's legacy buildscript-classpath form.
27
+ export function resolvePluginVersion(text, pluginId, catalog, classpathGroupArtifact) {
28
+ const direct = extractDirectIdVersion(text, pluginId);
29
+ if (direct)
30
+ return { version: direct };
31
+ if (classpathGroupArtifact) {
32
+ const classpath = extractClasspathVersion(text, classpathGroupArtifact);
33
+ if (classpath)
34
+ return { version: classpath };
35
+ }
36
+ const catalogVersion = extractCatalogAliasVersion(text, pluginId, catalog);
37
+ if (catalogVersion)
38
+ return { version: catalogVersion };
39
+ return {};
40
+ }