@invarn/cibuild 2.9.5 → 2.9.7

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 (553) hide show
  1. package/dist/chunk-35SI42CD.mjs +1 -0
  2. package/dist/chunk-IC45KVLJ.mjs +1 -0
  3. package/dist/chunk-XDY4V23V.mjs +1 -0
  4. package/dist/cli.cjs +14 -14
  5. package/dist/commands.mjs +1 -0
  6. package/dist/lib.mjs +3934 -0
  7. package/package.json +6 -10
  8. package/dist/src/cli.d.ts +0 -3
  9. package/dist/src/cli.d.ts.map +0 -1
  10. package/dist/src/cli.js +0 -399
  11. package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.d.ts +0 -38
  12. package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.d.ts.map +0 -1
  13. package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.js +0 -222
  14. package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.d.ts +0 -41
  15. package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.d.ts.map +0 -1
  16. package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.js +0 -324
  17. package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.d.ts +0 -2
  18. package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.d.ts.map +0 -1
  19. package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.js +0 -382
  20. package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts +0 -29
  21. package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts.map +0 -1
  22. package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.js +0 -160
  23. package/dist/src/commands/android-gms-guard.test.d.ts +0 -9
  24. package/dist/src/commands/android-gms-guard.test.d.ts.map +0 -1
  25. package/dist/src/commands/android-gms-guard.test.js +0 -78
  26. package/dist/src/commands/android-java-version.test.d.ts +0 -12
  27. package/dist/src/commands/android-java-version.test.d.ts.map +0 -1
  28. package/dist/src/commands/android-java-version.test.js +0 -209
  29. package/dist/src/commands/android-scanner-java.test.d.ts +0 -2
  30. package/dist/src/commands/android-scanner-java.test.d.ts.map +0 -1
  31. package/dist/src/commands/android-scanner-java.test.js +0 -130
  32. package/dist/src/commands/android-scanner.d.ts +0 -250
  33. package/dist/src/commands/android-scanner.d.ts.map +0 -1
  34. package/dist/src/commands/android-scanner.js +0 -1639
  35. package/dist/src/commands/build.d.ts +0 -215
  36. package/dist/src/commands/build.d.ts.map +0 -1
  37. package/dist/src/commands/build.js +0 -1433
  38. package/dist/src/commands/build.test.d.ts +0 -13
  39. package/dist/src/commands/build.test.d.ts.map +0 -1
  40. package/dist/src/commands/build.test.js +0 -121
  41. package/dist/src/commands/detect-platform.d.ts +0 -13
  42. package/dist/src/commands/detect-platform.d.ts.map +0 -1
  43. package/dist/src/commands/detect-platform.js +0 -51
  44. package/dist/src/commands/detect-project.d.ts +0 -7
  45. package/dist/src/commands/detect-project.d.ts.map +0 -1
  46. package/dist/src/commands/detect-project.js +0 -12
  47. package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.d.ts +0 -26
  48. package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.d.ts.map +0 -1
  49. package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.js +0 -196
  50. package/dist/src/commands/edit.d.ts +0 -12
  51. package/dist/src/commands/edit.d.ts.map +0 -1
  52. package/dist/src/commands/edit.js +0 -652
  53. package/dist/src/commands/edit.test.d.ts +0 -6
  54. package/dist/src/commands/edit.test.d.ts.map +0 -1
  55. package/dist/src/commands/edit.test.js +0 -45
  56. package/dist/src/commands/fidelity-trim.d.ts +0 -13
  57. package/dist/src/commands/fidelity-trim.d.ts.map +0 -1
  58. package/dist/src/commands/fidelity-trim.js +0 -21
  59. package/dist/src/commands/fidelity-trim.test.d.ts +0 -8
  60. package/dist/src/commands/fidelity-trim.test.d.ts.map +0 -1
  61. package/dist/src/commands/fidelity-trim.test.js +0 -79
  62. package/dist/src/commands/file-secret-collector.d.ts +0 -50
  63. package/dist/src/commands/file-secret-collector.d.ts.map +0 -1
  64. package/dist/src/commands/file-secret-collector.js +0 -217
  65. package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.d.ts +0 -38
  66. package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.d.ts.map +0 -1
  67. package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.js +0 -300
  68. package/dist/src/commands/github-workflow.d.ts +0 -5
  69. package/dist/src/commands/github-workflow.d.ts.map +0 -1
  70. package/dist/src/commands/github-workflow.js +0 -45
  71. package/dist/src/commands/index.d.ts +0 -33
  72. package/dist/src/commands/index.d.ts.map +0 -1
  73. package/dist/src/commands/index.js +0 -33
  74. package/dist/src/commands/init.d.ts +0 -20
  75. package/dist/src/commands/init.d.ts.map +0 -1
  76. package/dist/src/commands/init.js +0 -264
  77. package/dist/src/commands/ios-scanner.d.ts +0 -104
  78. package/dist/src/commands/ios-scanner.d.ts.map +0 -1
  79. package/dist/src/commands/ios-scanner.js +0 -492
  80. package/dist/src/commands/ios-scheme-ranking.test.d.ts +0 -2
  81. package/dist/src/commands/ios-scheme-ranking.test.d.ts.map +0 -1
  82. package/dist/src/commands/ios-scheme-ranking.test.js +0 -264
  83. package/dist/src/commands/local-properties-reaches-every-workflow.test.d.ts +0 -30
  84. package/dist/src/commands/local-properties-reaches-every-workflow.test.d.ts.map +0 -1
  85. package/dist/src/commands/local-properties-reaches-every-workflow.test.js +0 -146
  86. package/dist/src/commands/reset.d.ts +0 -7
  87. package/dist/src/commands/reset.d.ts.map +0 -1
  88. package/dist/src/commands/reset.js +0 -81
  89. package/dist/src/commands/run.d.ts +0 -22
  90. package/dist/src/commands/run.d.ts.map +0 -1
  91. package/dist/src/commands/run.js +0 -141
  92. package/dist/src/commands/secrets-sync-workflow.d.ts +0 -15
  93. package/dist/src/commands/secrets-sync-workflow.d.ts.map +0 -1
  94. package/dist/src/commands/secrets-sync-workflow.js +0 -255
  95. package/dist/src/commands/secrets-upload.d.ts +0 -21
  96. package/dist/src/commands/secrets-upload.d.ts.map +0 -1
  97. package/dist/src/commands/secrets-upload.js +0 -177
  98. package/dist/src/commands/secrets-upload.test.d.ts +0 -5
  99. package/dist/src/commands/secrets-upload.test.d.ts.map +0 -1
  100. package/dist/src/commands/secrets-upload.test.js +0 -60
  101. package/dist/src/commands/validate.d.ts +0 -10
  102. package/dist/src/commands/validate.d.ts.map +0 -1
  103. package/dist/src/commands/validate.js +0 -46
  104. package/dist/src/config.d.ts +0 -3
  105. package/dist/src/config.d.ts.map +0 -1
  106. package/dist/src/config.js +0 -47
  107. package/dist/src/envman/cli.d.ts +0 -21
  108. package/dist/src/envman/cli.d.ts.map +0 -1
  109. package/dist/src/envman/cli.js +0 -240
  110. package/dist/src/envman/envman.d.ts +0 -83
  111. package/dist/src/envman/envman.d.ts.map +0 -1
  112. package/dist/src/envman/envman.js +0 -361
  113. package/dist/src/envman/envman.test.d.ts +0 -5
  114. package/dist/src/envman/envman.test.d.ts.map +0 -1
  115. package/dist/src/envman/envman.test.js +0 -236
  116. package/dist/src/envman/envstore-round-trip.test.d.ts +0 -27
  117. package/dist/src/envman/envstore-round-trip.test.d.ts.map +0 -1
  118. package/dist/src/envman/envstore-round-trip.test.js +0 -130
  119. package/dist/src/envman/index.d.ts +0 -23
  120. package/dist/src/envman/index.d.ts.map +0 -1
  121. package/dist/src/envman/index.js +0 -23
  122. package/dist/src/envman/shim.d.ts +0 -16
  123. package/dist/src/envman/shim.d.ts.map +0 -1
  124. package/dist/src/envman/shim.js +0 -66
  125. package/dist/src/envman/types.d.ts +0 -55
  126. package/dist/src/envman/types.d.ts.map +0 -1
  127. package/dist/src/envman/types.js +0 -12
  128. package/dist/src/lib.d.ts +0 -33
  129. package/dist/src/lib.d.ts.map +0 -1
  130. package/dist/src/lib.js +0 -37
  131. package/dist/src/pipeline.d.ts +0 -3
  132. package/dist/src/pipeline.d.ts.map +0 -1
  133. package/dist/src/pipeline.js +0 -57
  134. package/dist/src/runner-node-step.test.d.ts +0 -12
  135. package/dist/src/runner-node-step.test.d.ts.map +0 -1
  136. package/dist/src/runner-node-step.test.js +0 -61
  137. package/dist/src/runner.d.ts +0 -31
  138. package/dist/src/runner.d.ts.map +0 -1
  139. package/dist/src/runner.js +0 -240
  140. package/dist/src/shared/detect-project.d.ts +0 -57
  141. package/dist/src/shared/detect-project.d.ts.map +0 -1
  142. package/dist/src/shared/detect-project.js +0 -219
  143. package/dist/src/shared/detect-project.test.d.ts +0 -2
  144. package/dist/src/shared/detect-project.test.d.ts.map +0 -1
  145. package/dist/src/shared/detect-project.test.js +0 -161
  146. package/dist/src/shared/git.d.ts +0 -25
  147. package/dist/src/shared/git.d.ts.map +0 -1
  148. package/dist/src/shared/git.js +0 -53
  149. package/dist/src/shared/git.test.d.ts +0 -9
  150. package/dist/src/shared/git.test.d.ts.map +0 -1
  151. package/dist/src/shared/git.test.js +0 -66
  152. package/dist/src/shared/gitignore.d.ts +0 -6
  153. package/dist/src/shared/gitignore.d.ts.map +0 -1
  154. package/dist/src/shared/gitignore.js +0 -26
  155. package/dist/src/shared/gradle-settings.d.ts +0 -49
  156. package/dist/src/shared/gradle-settings.d.ts.map +0 -1
  157. package/dist/src/shared/gradle-settings.js +0 -138
  158. package/dist/src/shared/gradle-settings.test.d.ts +0 -2
  159. package/dist/src/shared/gradle-settings.test.d.ts.map +0 -1
  160. package/dist/src/shared/gradle-settings.test.js +0 -65
  161. package/dist/src/shared/prompts.d.ts +0 -30
  162. package/dist/src/shared/prompts.d.ts.map +0 -1
  163. package/dist/src/shared/prompts.js +0 -121
  164. package/dist/src/shared/xcode-container.test.d.ts +0 -2
  165. package/dist/src/shared/xcode-container.test.d.ts.map +0 -1
  166. package/dist/src/shared/xcode-container.test.js +0 -123
  167. package/dist/src/step-marker.d.ts +0 -12
  168. package/dist/src/step-marker.d.ts.map +0 -1
  169. package/dist/src/step-marker.js +0 -29
  170. package/dist/src/step-marker.test.d.ts +0 -11
  171. package/dist/src/step-marker.test.d.ts.map +0 -1
  172. package/dist/src/step-marker.test.js +0 -45
  173. package/dist/src/types.d.ts +0 -58
  174. package/dist/src/types.d.ts.map +0 -1
  175. package/dist/src/types.js +0 -2
  176. package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.d.ts +0 -28
  177. package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.d.ts.map +0 -1
  178. package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.js +0 -223
  179. package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.d.ts +0 -20
  180. package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.d.ts.map +0 -1
  181. package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.js +0 -201
  182. package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.d.ts +0 -37
  183. package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.d.ts.map +0 -1
  184. package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.js +0 -255
  185. package/dist/src/yaml/a-missing-credential-is-loud.test.d.ts +0 -32
  186. package/dist/src/yaml/a-missing-credential-is-loud.test.d.ts.map +0 -1
  187. package/dist/src/yaml/a-missing-credential-is-loud.test.js +0 -301
  188. package/dist/src/yaml/a-runtime-requirement-names-a-real-provider.test.d.ts +0 -18
  189. package/dist/src/yaml/a-runtime-requirement-names-a-real-provider.test.d.ts.map +0 -1
  190. package/dist/src/yaml/a-runtime-requirement-names-a-real-provider.test.js +0 -66
  191. package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.d.ts +0 -34
  192. package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.d.ts.map +0 -1
  193. package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.js +0 -218
  194. package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.d.ts +0 -27
  195. package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.d.ts.map +0 -1
  196. package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.js +0 -126
  197. package/dist/src/yaml/bitrise-compat.d.ts +0 -65
  198. package/dist/src/yaml/bitrise-compat.d.ts.map +0 -1
  199. package/dist/src/yaml/bitrise-compat.js +0 -206
  200. package/dist/src/yaml/bitrise-compat.test.d.ts +0 -5
  201. package/dist/src/yaml/bitrise-compat.test.d.ts.map +0 -1
  202. package/dist/src/yaml/bitrise-compat.test.js +0 -347
  203. package/dist/src/yaml/cache-key-missing-file.test.d.ts +0 -12
  204. package/dist/src/yaml/cache-key-missing-file.test.d.ts.map +0 -1
  205. package/dist/src/yaml/cache-key-missing-file.test.js +0 -116
  206. package/dist/src/yaml/converter.d.ts +0 -103
  207. package/dist/src/yaml/converter.d.ts.map +0 -1
  208. package/dist/src/yaml/converter.js +0 -420
  209. package/dist/src/yaml/converter.test.d.ts +0 -5
  210. package/dist/src/yaml/converter.test.d.ts.map +0 -1
  211. package/dist/src/yaml/converter.test.js +0 -548
  212. package/dist/src/yaml/e2e.test.d.ts +0 -6
  213. package/dist/src/yaml/e2e.test.d.ts.map +0 -1
  214. package/dist/src/yaml/e2e.test.js +0 -446
  215. package/dist/src/yaml/env-resolver.d.ts +0 -218
  216. package/dist/src/yaml/env-resolver.d.ts.map +0 -1
  217. package/dist/src/yaml/env-resolver.js +0 -591
  218. package/dist/src/yaml/env-resolver.test.d.ts +0 -5
  219. package/dist/src/yaml/env-resolver.test.d.ts.map +0 -1
  220. package/dist/src/yaml/env-resolver.test.js +0 -502
  221. package/dist/src/yaml/every-build-log-witnesses-the-commit-on-disk.test.d.ts +0 -16
  222. package/dist/src/yaml/every-build-log-witnesses-the-commit-on-disk.test.d.ts.map +0 -1
  223. package/dist/src/yaml/every-build-log-witnesses-the-commit-on-disk.test.js +0 -126
  224. package/dist/src/yaml/fidelity-input-digest.d.ts +0 -37
  225. package/dist/src/yaml/fidelity-input-digest.d.ts.map +0 -1
  226. package/dist/src/yaml/fidelity-input-digest.js +0 -208
  227. package/dist/src/yaml/fidelity-input-digest.test.d.ts +0 -2
  228. package/dist/src/yaml/fidelity-input-digest.test.d.ts.map +0 -1
  229. package/dist/src/yaml/fidelity-input-digest.test.js +0 -167
  230. package/dist/src/yaml/fidelity-manifest.d.ts +0 -126
  231. package/dist/src/yaml/fidelity-manifest.d.ts.map +0 -1
  232. package/dist/src/yaml/fidelity-manifest.js +0 -134
  233. package/dist/src/yaml/fidelity-manifest.test.d.ts +0 -12
  234. package/dist/src/yaml/fidelity-manifest.test.d.ts.map +0 -1
  235. package/dist/src/yaml/fidelity-manifest.test.js +0 -251
  236. package/dist/src/yaml/interactive-prompts.d.ts +0 -71
  237. package/dist/src/yaml/interactive-prompts.d.ts.map +0 -1
  238. package/dist/src/yaml/interactive-prompts.js +0 -260
  239. package/dist/src/yaml/meta-helpers.d.ts +0 -28
  240. package/dist/src/yaml/meta-helpers.d.ts.map +0 -1
  241. package/dist/src/yaml/meta-helpers.js +0 -46
  242. package/dist/src/yaml/missing-env-handler.d.ts +0 -45
  243. package/dist/src/yaml/missing-env-handler.d.ts.map +0 -1
  244. package/dist/src/yaml/missing-env-handler.js +0 -64
  245. package/dist/src/yaml/parser.d.ts +0 -33
  246. package/dist/src/yaml/parser.d.ts.map +0 -1
  247. package/dist/src/yaml/parser.js +0 -148
  248. package/dist/src/yaml/parser.test.d.ts +0 -5
  249. package/dist/src/yaml/parser.test.d.ts.map +0 -1
  250. package/dist/src/yaml/parser.test.js +0 -83
  251. package/dist/src/yaml/pipeline-with-secrets.d.ts +0 -25
  252. package/dist/src/yaml/pipeline-with-secrets.d.ts.map +0 -1
  253. package/dist/src/yaml/pipeline-with-secrets.js +0 -76
  254. package/dist/src/yaml/platform-detector.d.ts +0 -83
  255. package/dist/src/yaml/platform-detector.d.ts.map +0 -1
  256. package/dist/src/yaml/platform-detector.js +0 -189
  257. package/dist/src/yaml/platform-detector.test.d.ts +0 -5
  258. package/dist/src/yaml/platform-detector.test.d.ts.map +0 -1
  259. package/dist/src/yaml/platform-detector.test.js +0 -525
  260. package/dist/src/yaml/preflight-validation.d.ts +0 -29
  261. package/dist/src/yaml/preflight-validation.d.ts.map +0 -1
  262. package/dist/src/yaml/preflight-validation.js +0 -21
  263. package/dist/src/yaml/secrets-manager.d.ts +0 -87
  264. package/dist/src/yaml/secrets-manager.d.ts.map +0 -1
  265. package/dist/src/yaml/secrets-manager.js +0 -230
  266. package/dist/src/yaml/shell-assignments.d.ts +0 -40
  267. package/dist/src/yaml/shell-assignments.d.ts.map +0 -1
  268. package/dist/src/yaml/shell-assignments.js +0 -77
  269. package/dist/src/yaml/step-validator-check-failure.test.d.ts +0 -24
  270. package/dist/src/yaml/step-validator-check-failure.test.d.ts.map +0 -1
  271. package/dist/src/yaml/step-validator-check-failure.test.js +0 -130
  272. package/dist/src/yaml/step-validator-self-assigned.test.d.ts +0 -23
  273. package/dist/src/yaml/step-validator-self-assigned.test.d.ts.map +0 -1
  274. package/dist/src/yaml/step-validator-self-assigned.test.js +0 -191
  275. package/dist/src/yaml/step-validator.d.ts +0 -67
  276. package/dist/src/yaml/step-validator.d.ts.map +0 -1
  277. package/dist/src/yaml/step-validator.js +0 -466
  278. package/dist/src/yaml/step-validator.test.d.ts +0 -18
  279. package/dist/src/yaml/step-validator.test.d.ts.map +0 -1
  280. package/dist/src/yaml/step-validator.test.js +0 -140
  281. package/dist/src/yaml/steps/android-sign.d.ts +0 -35
  282. package/dist/src/yaml/steps/android-sign.d.ts.map +0 -1
  283. package/dist/src/yaml/steps/android-sign.js +0 -147
  284. package/dist/src/yaml/steps/android-variant-resolution.test.d.ts +0 -2
  285. package/dist/src/yaml/steps/android-variant-resolution.test.d.ts.map +0 -1
  286. package/dist/src/yaml/steps/android-variant-resolution.test.js +0 -260
  287. package/dist/src/yaml/steps/android-version.d.ts +0 -26
  288. package/dist/src/yaml/steps/android-version.d.ts.map +0 -1
  289. package/dist/src/yaml/steps/android-version.js +0 -128
  290. package/dist/src/yaml/steps/android-version.test.d.ts +0 -5
  291. package/dist/src/yaml/steps/android-version.test.d.ts.map +0 -1
  292. package/dist/src/yaml/steps/android-version.test.js +0 -196
  293. package/dist/src/yaml/steps/android.d.ts +0 -156
  294. package/dist/src/yaml/steps/android.d.ts.map +0 -1
  295. package/dist/src/yaml/steps/android.js +0 -1092
  296. package/dist/src/yaml/steps/app-store-deploy.d.ts +0 -48
  297. package/dist/src/yaml/steps/app-store-deploy.d.ts.map +0 -1
  298. package/dist/src/yaml/steps/app-store-deploy.js +0 -167
  299. package/dist/src/yaml/steps/base.d.ts +0 -263
  300. package/dist/src/yaml/steps/base.d.ts.map +0 -1
  301. package/dist/src/yaml/steps/base.js +0 -380
  302. package/dist/src/yaml/steps/bitrise-android-tools.d.ts +0 -26
  303. package/dist/src/yaml/steps/bitrise-android-tools.d.ts.map +0 -1
  304. package/dist/src/yaml/steps/bitrise-android-tools.js +0 -200
  305. package/dist/src/yaml/steps/bitrise-android-tools.test.d.ts +0 -5
  306. package/dist/src/yaml/steps/bitrise-android-tools.test.d.ts.map +0 -1
  307. package/dist/src/yaml/steps/bitrise-android-tools.test.js +0 -280
  308. package/dist/src/yaml/steps/bitrise-apk-info.d.ts +0 -22
  309. package/dist/src/yaml/steps/bitrise-apk-info.d.ts.map +0 -1
  310. package/dist/src/yaml/steps/bitrise-apk-info.js +0 -144
  311. package/dist/src/yaml/steps/bitrise-apk-info.test.d.ts +0 -5
  312. package/dist/src/yaml/steps/bitrise-apk-info.test.d.ts.map +0 -1
  313. package/dist/src/yaml/steps/bitrise-apk-info.test.js +0 -331
  314. package/dist/src/yaml/steps/bitrise-slack.d.ts +0 -49
  315. package/dist/src/yaml/steps/bitrise-slack.d.ts.map +0 -1
  316. package/dist/src/yaml/steps/bitrise-slack.js +0 -280
  317. package/dist/src/yaml/steps/bitrise-slack.test.d.ts +0 -5
  318. package/dist/src/yaml/steps/bitrise-slack.test.d.ts.map +0 -1
  319. package/dist/src/yaml/steps/bitrise-slack.test.js +0 -484
  320. package/dist/src/yaml/steps/bitrise-ssh.d.ts +0 -27
  321. package/dist/src/yaml/steps/bitrise-ssh.d.ts.map +0 -1
  322. package/dist/src/yaml/steps/bitrise-ssh.js +0 -134
  323. package/dist/src/yaml/steps/bitrise-ssh.test.d.ts +0 -5
  324. package/dist/src/yaml/steps/bitrise-ssh.test.d.ts.map +0 -1
  325. package/dist/src/yaml/steps/bitrise-ssh.test.js +0 -205
  326. package/dist/src/yaml/steps/cache-pull-daemon.test.d.ts +0 -27
  327. package/dist/src/yaml/steps/cache-pull-daemon.test.d.ts.map +0 -1
  328. package/dist/src/yaml/steps/cache-pull-daemon.test.js +0 -212
  329. package/dist/src/yaml/steps/cache-pull-restore-roots.test.d.ts +0 -42
  330. package/dist/src/yaml/steps/cache-pull-restore-roots.test.d.ts.map +0 -1
  331. package/dist/src/yaml/steps/cache-pull-restore-roots.test.js +0 -209
  332. package/dist/src/yaml/steps/cache-push-daemon.test.d.ts +0 -27
  333. package/dist/src/yaml/steps/cache-push-daemon.test.d.ts.map +0 -1
  334. package/dist/src/yaml/steps/cache-push-daemon.test.js +0 -171
  335. package/dist/src/yaml/steps/cache.d.ts +0 -59
  336. package/dist/src/yaml/steps/cache.d.ts.map +0 -1
  337. package/dist/src/yaml/steps/cache.js +0 -1027
  338. package/dist/src/yaml/steps/capture-injection.d.ts +0 -30
  339. package/dist/src/yaml/steps/capture-injection.d.ts.map +0 -1
  340. package/dist/src/yaml/steps/capture-injection.js +0 -88
  341. package/dist/src/yaml/steps/capture-injection.test.d.ts +0 -2
  342. package/dist/src/yaml/steps/capture-injection.test.d.ts.map +0 -1
  343. package/dist/src/yaml/steps/capture-injection.test.js +0 -163
  344. package/dist/src/yaml/steps/cocoapods-deployment-floor.d.ts +0 -42
  345. package/dist/src/yaml/steps/cocoapods-deployment-floor.d.ts.map +0 -1
  346. package/dist/src/yaml/steps/cocoapods-deployment-floor.js +0 -140
  347. package/dist/src/yaml/steps/cocoapods-deployment-floor.test.d.ts +0 -11
  348. package/dist/src/yaml/steps/cocoapods-deployment-floor.test.d.ts.map +0 -1
  349. package/dist/src/yaml/steps/cocoapods-deployment-floor.test.js +0 -176
  350. package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.d.ts +0 -22
  351. package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.d.ts.map +0 -1
  352. package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.js +0 -97
  353. package/dist/src/yaml/steps/cocoapods-locked-bundler.test.d.ts +0 -17
  354. package/dist/src/yaml/steps/cocoapods-locked-bundler.test.d.ts.map +0 -1
  355. package/dist/src/yaml/steps/cocoapods-locked-bundler.test.js +0 -282
  356. package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.d.ts +0 -17
  357. package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.d.ts.map +0 -1
  358. package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.js +0 -132
  359. package/dist/src/yaml/steps/envstore-loader.test.d.ts +0 -15
  360. package/dist/src/yaml/steps/envstore-loader.test.d.ts.map +0 -1
  361. package/dist/src/yaml/steps/envstore-loader.test.js +0 -143
  362. package/dist/src/yaml/steps/fastlane.d.ts +0 -27
  363. package/dist/src/yaml/steps/fastlane.d.ts.map +0 -1
  364. package/dist/src/yaml/steps/fastlane.js +0 -79
  365. package/dist/src/yaml/steps/fidelity-aspect-guard.d.ts +0 -53
  366. package/dist/src/yaml/steps/fidelity-aspect-guard.d.ts.map +0 -1
  367. package/dist/src/yaml/steps/fidelity-aspect-guard.js +0 -72
  368. package/dist/src/yaml/steps/fidelity-aspect-guard.test.d.ts +0 -11
  369. package/dist/src/yaml/steps/fidelity-aspect-guard.test.d.ts.map +0 -1
  370. package/dist/src/yaml/steps/fidelity-aspect-guard.test.js +0 -50
  371. package/dist/src/yaml/steps/fidelity-reference-provenance.d.ts +0 -87
  372. package/dist/src/yaml/steps/fidelity-reference-provenance.d.ts.map +0 -1
  373. package/dist/src/yaml/steps/fidelity-reference-provenance.js +0 -111
  374. package/dist/src/yaml/steps/fidelity-reference-provenance.test.d.ts +0 -22
  375. package/dist/src/yaml/steps/fidelity-reference-provenance.test.d.ts.map +0 -1
  376. package/dist/src/yaml/steps/fidelity-reference-provenance.test.js +0 -118
  377. package/dist/src/yaml/steps/file.d.ts +0 -52
  378. package/dist/src/yaml/steps/file.d.ts.map +0 -1
  379. package/dist/src/yaml/steps/file.js +0 -108
  380. package/dist/src/yaml/steps/flutter.d.ts +0 -63
  381. package/dist/src/yaml/steps/flutter.d.ts.map +0 -1
  382. package/dist/src/yaml/steps/flutter.js +0 -215
  383. package/dist/src/yaml/steps/git-clone-names-the-inputs-it-ignores.test.d.ts +0 -16
  384. package/dist/src/yaml/steps/git-clone-names-the-inputs-it-ignores.test.d.ts.map +0 -1
  385. package/dist/src/yaml/steps/git-clone-names-the-inputs-it-ignores.test.js +0 -127
  386. package/dist/src/yaml/steps/git-clone.d.ts +0 -32
  387. package/dist/src/yaml/steps/git-clone.d.ts.map +0 -1
  388. package/dist/src/yaml/steps/git-clone.js +0 -171
  389. package/dist/src/yaml/steps/git-fetch-retry-exec.test.d.ts +0 -10
  390. package/dist/src/yaml/steps/git-fetch-retry-exec.test.d.ts.map +0 -1
  391. package/dist/src/yaml/steps/git-fetch-retry-exec.test.js +0 -162
  392. package/dist/src/yaml/steps/git-fetch-retry.d.ts +0 -62
  393. package/dist/src/yaml/steps/git-fetch-retry.d.ts.map +0 -1
  394. package/dist/src/yaml/steps/git-fetch-retry.js +0 -155
  395. package/dist/src/yaml/steps/git-fetch-retry.test.d.ts +0 -5
  396. package/dist/src/yaml/steps/git-fetch-retry.test.d.ts.map +0 -1
  397. package/dist/src/yaml/steps/git-fetch-retry.test.js +0 -86
  398. package/dist/src/yaml/steps/google-play-deploy.d.ts +0 -37
  399. package/dist/src/yaml/steps/google-play-deploy.d.ts.map +0 -1
  400. package/dist/src/yaml/steps/google-play-deploy.js +0 -193
  401. package/dist/src/yaml/steps/google-play-deploy.test.d.ts +0 -5
  402. package/dist/src/yaml/steps/google-play-deploy.test.d.ts.map +0 -1
  403. package/dist/src/yaml/steps/google-play-deploy.test.js +0 -310
  404. package/dist/src/yaml/steps/gradle-memory.d.ts +0 -61
  405. package/dist/src/yaml/steps/gradle-memory.d.ts.map +0 -1
  406. package/dist/src/yaml/steps/gradle-memory.js +0 -386
  407. package/dist/src/yaml/steps/gradle-memory.test.d.ts +0 -2
  408. package/dist/src/yaml/steps/gradle-memory.test.d.ts.map +0 -1
  409. package/dist/src/yaml/steps/gradle-memory.test.js +0 -344
  410. package/dist/src/yaml/steps/gradle-sdk-cmake.d.ts +0 -28
  411. package/dist/src/yaml/steps/gradle-sdk-cmake.d.ts.map +0 -1
  412. package/dist/src/yaml/steps/gradle-sdk-cmake.js +0 -107
  413. package/dist/src/yaml/steps/gradle-sdk-cmake.test.d.ts +0 -2
  414. package/dist/src/yaml/steps/gradle-sdk-cmake.test.d.ts.map +0 -1
  415. package/dist/src/yaml/steps/gradle-sdk-cmake.test.js +0 -183
  416. package/dist/src/yaml/steps/index.d.ts +0 -10
  417. package/dist/src/yaml/steps/index.d.ts.map +0 -1
  418. package/dist/src/yaml/steps/index.js +0 -1461
  419. package/dist/src/yaml/steps/ios-deps.d.ts +0 -78
  420. package/dist/src/yaml/steps/ios-deps.d.ts.map +0 -1
  421. package/dist/src/yaml/steps/ios-deps.js +0 -279
  422. package/dist/src/yaml/steps/ios-deps.test.d.ts +0 -5
  423. package/dist/src/yaml/steps/ios-deps.test.d.ts.map +0 -1
  424. package/dist/src/yaml/steps/ios-deps.test.js +0 -90
  425. package/dist/src/yaml/steps/ios-signing.d.ts +0 -31
  426. package/dist/src/yaml/steps/ios-signing.d.ts.map +0 -1
  427. package/dist/src/yaml/steps/ios-signing.js +0 -144
  428. package/dist/src/yaml/steps/ios-version.d.ts +0 -47
  429. package/dist/src/yaml/steps/ios-version.d.ts.map +0 -1
  430. package/dist/src/yaml/steps/ios-version.js +0 -151
  431. package/dist/src/yaml/steps/java-home-version-spec.test.d.ts +0 -10
  432. package/dist/src/yaml/steps/java-home-version-spec.test.d.ts.map +0 -1
  433. package/dist/src/yaml/steps/java-home-version-spec.test.js +0 -58
  434. package/dist/src/yaml/steps/linting.d.ts +0 -47
  435. package/dist/src/yaml/steps/linting.d.ts.map +0 -1
  436. package/dist/src/yaml/steps/linting.js +0 -148
  437. package/dist/src/yaml/steps/phase2.test.d.ts +0 -6
  438. package/dist/src/yaml/steps/phase2.test.d.ts.map +0 -1
  439. package/dist/src/yaml/steps/phase2.test.js +0 -197
  440. package/dist/src/yaml/steps/phase3.test.d.ts +0 -5
  441. package/dist/src/yaml/steps/phase3.test.d.ts.map +0 -1
  442. package/dist/src/yaml/steps/phase3.test.js +0 -144
  443. package/dist/src/yaml/steps/phase4.test.d.ts +0 -5
  444. package/dist/src/yaml/steps/phase4.test.d.ts.map +0 -1
  445. package/dist/src/yaml/steps/phase4.test.js +0 -166
  446. package/dist/src/yaml/steps/phase5.test.d.ts +0 -6
  447. package/dist/src/yaml/steps/phase5.test.d.ts.map +0 -1
  448. package/dist/src/yaml/steps/phase5.test.js +0 -263
  449. package/dist/src/yaml/steps/registry.d.ts +0 -88
  450. package/dist/src/yaml/steps/registry.d.ts.map +0 -1
  451. package/dist/src/yaml/steps/registry.js +0 -125
  452. package/dist/src/yaml/steps/registry.test.d.ts +0 -5
  453. package/dist/src/yaml/steps/registry.test.d.ts.map +0 -1
  454. package/dist/src/yaml/steps/registry.test.js +0 -235
  455. package/dist/src/yaml/steps/release.d.ts +0 -50
  456. package/dist/src/yaml/steps/release.d.ts.map +0 -1
  457. package/dist/src/yaml/steps/release.js +0 -154
  458. package/dist/src/yaml/steps/render-post-processor.d.ts +0 -100
  459. package/dist/src/yaml/steps/render-post-processor.d.ts.map +0 -1
  460. package/dist/src/yaml/steps/render-post-processor.js +0 -270
  461. package/dist/src/yaml/steps/render-post-processor.test.d.ts +0 -13
  462. package/dist/src/yaml/steps/render-post-processor.test.d.ts.map +0 -1
  463. package/dist/src/yaml/steps/render-post-processor.test.js +0 -220
  464. package/dist/src/yaml/steps/script.d.ts +0 -23
  465. package/dist/src/yaml/steps/script.d.ts.map +0 -1
  466. package/dist/src/yaml/steps/script.js +0 -63
  467. package/dist/src/yaml/steps/spec-validation.test.d.ts +0 -6
  468. package/dist/src/yaml/steps/spec-validation.test.d.ts.map +0 -1
  469. package/dist/src/yaml/steps/spec-validation.test.js +0 -130
  470. package/dist/src/yaml/steps/steps.test.d.ts +0 -6
  471. package/dist/src/yaml/steps/steps.test.d.ts.map +0 -1
  472. package/dist/src/yaml/steps/steps.test.js +0 -881
  473. package/dist/src/yaml/steps/structural-facts.d.ts +0 -992
  474. package/dist/src/yaml/steps/structural-facts.d.ts.map +0 -1
  475. package/dist/src/yaml/steps/structural-facts.js +0 -3276
  476. package/dist/src/yaml/steps/structural-facts.test.d.ts +0 -12
  477. package/dist/src/yaml/steps/structural-facts.test.d.ts.map +0 -1
  478. package/dist/src/yaml/steps/structural-facts.test.js +0 -3039
  479. package/dist/src/yaml/steps/test-config.d.ts +0 -5
  480. package/dist/src/yaml/steps/test-config.d.ts.map +0 -1
  481. package/dist/src/yaml/steps/test-config.js +0 -25
  482. package/dist/src/yaml/steps/trim-align-corpus.test.d.ts +0 -28
  483. package/dist/src/yaml/steps/trim-align-corpus.test.d.ts.map +0 -1
  484. package/dist/src/yaml/steps/trim-align-corpus.test.js +0 -383
  485. package/dist/src/yaml/steps/trim-align.d.ts +0 -87
  486. package/dist/src/yaml/steps/trim-align.d.ts.map +0 -1
  487. package/dist/src/yaml/steps/trim-align.js +0 -367
  488. package/dist/src/yaml/steps/trim-align.test.d.ts +0 -11
  489. package/dist/src/yaml/steps/trim-align.test.d.ts.map +0 -1
  490. package/dist/src/yaml/steps/trim-align.test.js +0 -261
  491. package/dist/src/yaml/steps/ui-fidelity-preview-android.d.ts +0 -66
  492. package/dist/src/yaml/steps/ui-fidelity-preview-android.d.ts.map +0 -1
  493. package/dist/src/yaml/steps/ui-fidelity-preview-android.js +0 -251
  494. package/dist/src/yaml/steps/ui-fidelity-preview-android.test.d.ts +0 -13
  495. package/dist/src/yaml/steps/ui-fidelity-preview-android.test.d.ts.map +0 -1
  496. package/dist/src/yaml/steps/ui-fidelity-preview-android.test.js +0 -302
  497. package/dist/src/yaml/steps/ui-fidelity-preview.d.ts +0 -134
  498. package/dist/src/yaml/steps/ui-fidelity-preview.d.ts.map +0 -1
  499. package/dist/src/yaml/steps/ui-fidelity-preview.js +0 -173
  500. package/dist/src/yaml/steps/ui-fidelity-preview.test.d.ts +0 -11
  501. package/dist/src/yaml/steps/ui-fidelity-preview.test.d.ts.map +0 -1
  502. package/dist/src/yaml/steps/ui-fidelity-preview.test.js +0 -210
  503. package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts +0 -155
  504. package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts.map +0 -1
  505. package/dist/src/yaml/steps/ui-fidelity-render-android.js +0 -1389
  506. package/dist/src/yaml/steps/ui-fidelity-render-android.test.d.ts +0 -2
  507. package/dist/src/yaml/steps/ui-fidelity-render-android.test.d.ts.map +0 -1
  508. package/dist/src/yaml/steps/ui-fidelity-render-android.test.js +0 -1106
  509. package/dist/src/yaml/steps/ui-fidelity-render.d.ts +0 -230
  510. package/dist/src/yaml/steps/ui-fidelity-render.d.ts.map +0 -1
  511. package/dist/src/yaml/steps/ui-fidelity-render.js +0 -1597
  512. package/dist/src/yaml/steps/ui-fidelity-render.test.d.ts +0 -18
  513. package/dist/src/yaml/steps/ui-fidelity-render.test.d.ts.map +0 -1
  514. package/dist/src/yaml/steps/ui-fidelity-render.test.js +0 -1871
  515. package/dist/src/yaml/steps/xcode-app-product.d.ts +0 -90
  516. package/dist/src/yaml/steps/xcode-app-product.d.ts.map +0 -1
  517. package/dist/src/yaml/steps/xcode-app-product.js +0 -247
  518. package/dist/src/yaml/steps/xcode-app-product.test.d.ts +0 -2
  519. package/dist/src/yaml/steps/xcode-app-product.test.d.ts.map +0 -1
  520. package/dist/src/yaml/steps/xcode-app-product.test.js +0 -202
  521. package/dist/src/yaml/steps/xcode-derived-data.test.d.ts +0 -30
  522. package/dist/src/yaml/steps/xcode-derived-data.test.d.ts.map +0 -1
  523. package/dist/src/yaml/steps/xcode-derived-data.test.js +0 -279
  524. package/dist/src/yaml/steps/xcode-destination.d.ts +0 -47
  525. package/dist/src/yaml/steps/xcode-destination.d.ts.map +0 -1
  526. package/dist/src/yaml/steps/xcode-destination.js +0 -202
  527. package/dist/src/yaml/steps/xcode-destination.test.d.ts +0 -9
  528. package/dist/src/yaml/steps/xcode-destination.test.d.ts.map +0 -1
  529. package/dist/src/yaml/steps/xcode-destination.test.js +0 -227
  530. package/dist/src/yaml/steps/xcode-new.test.d.ts +0 -5
  531. package/dist/src/yaml/steps/xcode-new.test.d.ts.map +0 -1
  532. package/dist/src/yaml/steps/xcode-new.test.js +0 -211
  533. package/dist/src/yaml/steps/xcode-resolve-retry.test.d.ts +0 -9
  534. package/dist/src/yaml/steps/xcode-resolve-retry.test.d.ts.map +0 -1
  535. package/dist/src/yaml/steps/xcode-resolve-retry.test.js +0 -146
  536. package/dist/src/yaml/steps/xcode-simulator-archs.d.ts +0 -53
  537. package/dist/src/yaml/steps/xcode-simulator-archs.d.ts.map +0 -1
  538. package/dist/src/yaml/steps/xcode-simulator-archs.js +0 -98
  539. package/dist/src/yaml/steps/xcode-simulator-archs.test.d.ts +0 -21
  540. package/dist/src/yaml/steps/xcode-simulator-archs.test.d.ts.map +0 -1
  541. package/dist/src/yaml/steps/xcode-simulator-archs.test.js +0 -155
  542. package/dist/src/yaml/steps/xcode.d.ts +0 -230
  543. package/dist/src/yaml/steps/xcode.d.ts.map +0 -1
  544. package/dist/src/yaml/steps/xcode.js +0 -1350
  545. package/dist/src/yaml/types.d.ts +0 -76
  546. package/dist/src/yaml/types.d.ts.map +0 -1
  547. package/dist/src/yaml/types.js +0 -5
  548. package/dist/src/yaml/validation-types.d.ts +0 -102
  549. package/dist/src/yaml/validation-types.d.ts.map +0 -1
  550. package/dist/src/yaml/validation-types.js +0 -8
  551. package/dist/src/yaml/yaml-updater.d.ts +0 -24
  552. package/dist/src/yaml/yaml-updater.d.ts.map +0 -1
  553. package/dist/src/yaml/yaml-updater.js +0 -128
@@ -1,1639 +0,0 @@
1
- import { resolve, join, relative, dirname } from "node:path";
2
- import { existsSync, readFileSync, readdirSync } from "node:fs";
3
- import { parseSettingsModules } from "../shared/gradle-settings.js";
4
- // ---------------------------------------------------------------------------
5
- // File discovery
6
- // ---------------------------------------------------------------------------
7
- /**
8
- * Returns the list of gradle files to scan.
9
- * Always includes the root build.gradle and the app/ module.
10
- * Also parses settings.gradle to find any additional include'd modules.
11
- */
12
- function findGradleFiles(root) {
13
- const candidates = [];
14
- // Root-level build files
15
- for (const name of ["build.gradle", "build.gradle.kts"]) {
16
- const p = resolve(root, name);
17
- if (existsSync(p))
18
- candidates.push(p);
19
- }
20
- // Collect module directories from settings.gradle
21
- const moduleDirs = new Set(["app"]); // always include app/
22
- for (const name of ["settings.gradle", "settings.gradle.kts"]) {
23
- const p = resolve(root, name);
24
- if (!existsSync(p))
25
- continue;
26
- const content = readFileSync(p, "utf-8");
27
- // One parser, shared with the detector. The regex that used to live here
28
- // matched only the *first* argument of an `include`, so
29
- // `include(":android", ":common", …)` — one call, eight modules in
30
- // `SimonSchubert/LinuxCommandLibrary` — was read as one, and the module
31
- // holding `jvmToolchain(21)` was never opened.
32
- for (const mod of parseSettingsModules(content))
33
- moduleDirs.add(mod);
34
- // An included build is a Gradle project of its own, and a monorepo's
35
- // convention plugins live in one. Its build file is where the Java level
36
- // for every module it configures is often declared.
37
- for (const included of parseIncludedBuilds(content)) {
38
- for (const buildFile of ["build.gradle", "build.gradle.kts"]) {
39
- const q = resolve(root, included, buildFile);
40
- if (existsSync(q))
41
- candidates.push(q);
42
- }
43
- }
44
- }
45
- for (const mod of moduleDirs) {
46
- for (const name of ["build.gradle", "build.gradle.kts"]) {
47
- const p = resolve(root, mod, name);
48
- if (existsSync(p))
49
- candidates.push(p);
50
- }
51
- }
52
- return candidates;
53
- }
54
- function safeRead(path) {
55
- try {
56
- return readFileSync(path, "utf-8");
57
- }
58
- catch {
59
- return "";
60
- }
61
- }
62
- function relPath(root, filePath) {
63
- return relative(root, filePath);
64
- }
65
- /**
66
- * Returns line numbers for each match of a regex in file content.
67
- */
68
- function lineNumber(content, index) {
69
- return content.slice(0, index).split("\n").length;
70
- }
71
- /** Extract all System.getenv("VAR") references. */
72
- function extractEnvVarRefs(content) {
73
- const results = [];
74
- const re = /System\.getenv\(\s*["']([^"']+)["']\s*\)/g;
75
- let m;
76
- while ((m = re.exec(content)) !== null) {
77
- results.push({ name: m[1], line: lineNumber(content, m.index) });
78
- }
79
- return results;
80
- }
81
- /** Extract Groovy findProperty("prop") and Kotlin properties["prop"] references. */
82
- function extractPropertyRefs(content) {
83
- const results = [];
84
- // Groovy: project.findProperty("name") or findProperty("name")
85
- const groovyRe = /(?:project\.)?findProperty\(\s*["']([^"']+)["']\s*\)/g;
86
- let m;
87
- while ((m = groovyRe.exec(content)) !== null) {
88
- results.push({ name: m[1], line: lineNumber(content, m.index) });
89
- }
90
- // Kotlin DSL: properties["name"] or project.properties["name"]
91
- const kotlinRe = /(?:project\.)?properties\["([^"]+)"\]/g;
92
- while ((m = kotlinRe.exec(content)) !== null) {
93
- results.push({ name: m[1], line: lineNumber(content, m.index) });
94
- }
95
- return results;
96
- }
97
- /**
98
- * Extract env var names from buildConfigField entries.
99
- * e.g. buildConfigField "String", "API_KEY", System.getenv("MAPS_API_KEY") ?: ""
100
- * Returns the System.getenv variable names embedded inside buildConfigField values.
101
- */
102
- function extractBuildConfigEnvRefs(content) {
103
- const results = [];
104
- // Match buildConfigField lines that also contain System.getenv
105
- const lineRe = /buildConfigField\s*[\("]/g;
106
- let m;
107
- const envRe = /System\.getenv\(\s*["']([^"']+)["']\s*\)/g;
108
- while ((m = lineRe.exec(content)) !== null) {
109
- // Find the end of the statement (next newline or semicolon)
110
- const lineEnd = content.indexOf("\n", m.index);
111
- const slice = content.slice(m.index, lineEnd === -1 ? undefined : lineEnd + 1);
112
- let em;
113
- while ((em = envRe.exec(slice)) !== null) {
114
- results.push({ name: em[1], line: lineNumber(content, m.index) });
115
- }
116
- envRe.lastIndex = 0;
117
- }
118
- return results;
119
- }
120
- /**
121
- * Returns true if the Google Secrets Gradle Plugin is applied.
122
- * This plugin reads local.properties (or a configured file) and auto-generates
123
- * BuildConfig fields — so if that file is absent in CI, fields won't be generated.
124
- */
125
- function detectSecretsPlugin(content) {
126
- return (/alias\s*\(\s*libs\.plugins\.secrets\s*\)/.test(content) ||
127
- /id\s*\(\s*["']com\.google\.android\.libraries\.mapsplatform\.secrets-gradle-plugin["']\s*\)/.test(content) ||
128
- /id\s*["']com\.google\.android\.libraries\.mapsplatform\.secrets-gradle-plugin["']/.test(content));
129
- }
130
- /**
131
- * Extracts the `propertiesFileName` from a `secrets { }` block.
132
- * Defaults to "local.properties" if not configured.
133
- */
134
- function extractSecretsPropertiesFile(content) {
135
- const block = extractBlockContent(content, "secrets");
136
- if (!block)
137
- return "local.properties";
138
- const m = /propertiesFileName\s*=\s*["']([^"']+)["']/.exec(block);
139
- return m ? m[1] : "local.properties";
140
- }
141
- // ---------------------------------------------------------------------------
142
- // Property keys the build reads out of the Secrets plugin's properties file
143
- // ---------------------------------------------------------------------------
144
- /**
145
- * True when this build file *applies* the Secrets plugin, as opposed to merely
146
- * declaring its version for other modules.
147
- *
148
- * A root build file almost always carries
149
- * `alias(libs.plugins.secrets) apply false` — the plugin is named there so a
150
- * module can apply it without repeating the version, and the root project
151
- * itself never loads a properties file. Reading that as "the root module
152
- * applies it" would aim the source walk below at the whole repository on the
153
- * strength of a line that applies nothing.
154
- *
155
- * Separate from `detectSecretsPlugin` on purpose: that one decides which
156
- * warnings are emitted, and this must not change them.
157
- */
158
- function appliesSecretsPlugin(content) {
159
- const applied = content
160
- .split("\n")
161
- .filter((line) => !/\bapply\s+false\b/.test(line))
162
- .join("\n");
163
- return detectSecretsPlugin(applied);
164
- }
165
- /**
166
- * The `BuildConfig` fields the Android Gradle Plugin generates by itself.
167
- *
168
- * `BuildConfig.DEBUG` is the most-referenced constant in Android source, and
169
- * every one of these is generated whether or not a properties file exists — so
170
- * naming them costs more than the unused line the rest of this scan tolerates.
171
- * A property named `DEBUG` would make the Secrets plugin generate a `String
172
- * DEBUG` beside AGP's `boolean DEBUG`, and the build that was going to compile
173
- * stops compiling because of the file meant to rescue it.
174
- *
175
- * `FLAVOR_<dimension>` is generated once per flavor dimension, so it is matched
176
- * by prefix rather than listed.
177
- */
178
- const AGP_GENERATED_BUILD_CONFIG_FIELDS = new Set([
179
- "DEBUG",
180
- "APPLICATION_ID",
181
- "BUILD_TYPE",
182
- "FLAVOR",
183
- "VERSION_CODE",
184
- "VERSION_NAME",
185
- "LIBRARY_PACKAGE_NAME",
186
- ]);
187
- function isAgpGeneratedField(name) {
188
- return AGP_GENERATED_BUILD_CONFIG_FIELDS.has(name) || name.startsWith("FLAVOR_");
189
- }
190
- /**
191
- * `BuildConfig.SOME_FIELD` references in a source file.
192
- *
193
- * Screaming snake case only: that is the convention for a generated constant,
194
- * and widening it to any identifier would pull in `BuildConfig.javaClass` and
195
- * every other member access on the class itself.
196
- */
197
- function extractBuildConfigFieldRefs(content) {
198
- const names = [];
199
- const re = /\bBuildConfig\.([A-Z][A-Z0-9_]*)\b/g;
200
- let m;
201
- while ((m = re.exec(content)) !== null) {
202
- if (!isAgpGeneratedField(m[1]))
203
- names.push(m[1]);
204
- }
205
- return names;
206
- }
207
- /**
208
- * The index of the character closing the bracket or parenthesis at `open`,
209
- * ignoring anything inside a string literal. -1 when it is never closed.
210
- */
211
- function matchingClose(content, open) {
212
- const closing = content[open] === "[" ? "]" : ")";
213
- let depth = 0;
214
- let quote = "";
215
- for (let i = open; i < content.length; i++) {
216
- const ch = content[i];
217
- if (quote) {
218
- if (ch === "\\")
219
- i++;
220
- else if (ch === quote)
221
- quote = "";
222
- continue;
223
- }
224
- if (ch === '"' || ch === "'")
225
- quote = ch;
226
- else if (ch === "[" || ch === "(")
227
- depth++;
228
- else if (ch === "]" || ch === ")") {
229
- depth--;
230
- if (depth === 0)
231
- return content[i] === closing ? i : -1;
232
- }
233
- }
234
- return -1;
235
- }
236
- /** Split a map-literal body on its top-level commas — not ones nested or quoted. */
237
- function splitTopLevel(body) {
238
- const parts = [];
239
- let depth = 0;
240
- let quote = "";
241
- let start = 0;
242
- for (let i = 0; i < body.length; i++) {
243
- const ch = body[i];
244
- if (quote) {
245
- if (ch === "\\")
246
- i++;
247
- else if (ch === quote)
248
- quote = "";
249
- continue;
250
- }
251
- if (ch === '"' || ch === "'")
252
- quote = ch;
253
- else if (ch === "[" || ch === "(" || ch === "{")
254
- depth++;
255
- else if (ch === "]" || ch === ")" || ch === "}")
256
- depth--;
257
- else if (ch === "," && depth === 0) {
258
- parts.push(body.slice(start, i));
259
- start = i + 1;
260
- }
261
- }
262
- parts.push(body.slice(start));
263
- return parts;
264
- }
265
- /**
266
- * The keys of a map literal, in either language's spelling:
267
- * Groovy `[key: v, "key": v]`, Kotlin `mapOf("key" to v)`.
268
- */
269
- function mapLiteralKeys(body) {
270
- const names = [];
271
- for (const entry of splitTopLevel(body)) {
272
- const kotlin = /^\s*["']([^"']+)["']\s+to\s/.exec(entry);
273
- if (kotlin) {
274
- names.push(kotlin[1]);
275
- continue;
276
- }
277
- const groovy = /^\s*(?:["']([^"']+)["']|([A-Za-z_]\w*))\s*:/.exec(entry);
278
- if (groovy)
279
- names.push(groovy[1] ?? groovy[2]);
280
- }
281
- return names;
282
- }
283
- /**
284
- * Keys named in a `manifestPlaceholders` map in a build file.
285
- *
286
- * The Secrets plugin fills a placeholder from the property of the same name,
287
- * so a placeholder the build declares is a property the build reads. Every
288
- * spelling the two DSLs offer for the same map:
289
- *
290
- * manifestPlaceholders["mapsApiKey"] = … // both
291
- * manifestPlaceholders.put("mapsApiKey", …) // Groovy
292
- * manifestPlaceholders = [mapsApiKey: …] // Groovy
293
- * manifestPlaceholders += mapOf("mapsApiKey" to …) // Kotlin
294
- */
295
- function extractManifestPlaceholderKeys(content) {
296
- const names = [];
297
- for (const re of [
298
- /manifestPlaceholders\s*\[\s*["']([^"']+)["']\s*\]/g,
299
- /manifestPlaceholders\s*\.\s*put\s*\(\s*["']([^"']+)["']/g,
300
- ]) {
301
- let m;
302
- while ((m = re.exec(content)) !== null)
303
- names.push(m[1]);
304
- }
305
- // A whole map assigned, added or put at once. The opening bracket the regex
306
- // ends on is where the literal starts; its match is where it ends.
307
- const assigned = /manifestPlaceholders\s*(?:\.\s*putAll\s*\(\s*)?(?:\+?=\s*)?(?:mapOf\s*|mutableMapOf\s*)?[[(]/g;
308
- let m;
309
- while ((m = assigned.exec(content)) !== null) {
310
- const open = m.index + m[0].length - 1;
311
- const close = matchingClose(content, open);
312
- if (close === -1)
313
- continue;
314
- names.push(...mapLiteralKeys(content.slice(open + 1, close)));
315
- assigned.lastIndex = close;
316
- }
317
- return names;
318
- }
319
- /**
320
- * Placeholder names referenced in an `AndroidManifest.xml`, other than the ones
321
- * the Android Gradle Plugin injects itself.
322
- *
323
- * **This is what the Secrets plugin is mostly for.** Its own documentation, and
324
- * every Google Maps setup guide, puts the key in the manifest and nowhere else:
325
- *
326
- * <meta-data android:name="com.google.android.geo.API_KEY"
327
- * android:value="${MAPS_API_KEY}" />
328
- *
329
- * There is no `BuildConfig` reference and no `manifestPlaceholders` block —
330
- * the plugin reads `MAPS_API_KEY` from the properties file and substitutes it
331
- * during manifest merging. A scan that reads only build files and source sees
332
- * nothing at all in such a repository.
333
- *
334
- * `applicationId` is excluded because AGP supplies it from the variant. A name
335
- * a build file *declares* in `manifestPlaceholders` is not excluded: the build
336
- * supplies that one, so naming it costs the usual unused line, and telling the
337
- * two apart would mean resolving which module's manifest merges with which
338
- * module's build file.
339
- */
340
- const AGP_INJECTED_PLACEHOLDERS = new Set(["applicationId"]);
341
- function extractManifestXmlPlaceholders(content) {
342
- const names = [];
343
- const re = /\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g;
344
- let m;
345
- while ((m = re.exec(content)) !== null) {
346
- if (!AGP_INJECTED_PLACEHOLDERS.has(m[1]))
347
- names.push(m[1]);
348
- }
349
- return names;
350
- }
351
- /**
352
- * True when this build file loads the properties file itself, rather than
353
- * leaving it to the Secrets plugin.
354
- *
355
- * Every spelling seen in the wild reduces to naming the file or calling a
356
- * helper that names it: `rootProject.file("local.properties")`, AGP's own
357
- * `gradleLocalProperties(rootDir)`, and Compose's `localPropertiesFile`.
358
- */
359
- function readsPropertiesFileDirectly(content) {
360
- return (/\blocal\.properties\b/.test(content) ||
361
- /\bgradleLocalProperties\s*\(/.test(content) ||
362
- /\blocalPropertiesFile\b/.test(content));
363
- }
364
- /**
365
- * Keys a build file reads out of a properties file it loaded itself.
366
- *
367
- * `getProperty("x")` is the direct form, with or without a default. The rest is
368
- * for the idiom that hides it: a repository defines its own one-line accessor —
369
- *
370
- * def getProps(String name) {
371
- * def f = rootProject.file('local.properties')
372
- * if (f.exists()) { def p = new Properties(); p.load(...); return p[name] }
373
- * return ""
374
- * }
375
- *
376
- * — and every key then arrives as `getProps("sign.file")`, under a name chosen
377
- * by that repository. So a function whose *body* reads the properties file is
378
- * treated as an accessor for it, and the string literals at its call sites are
379
- * keys. Without that, a repository like this reads as having none.
380
- *
381
- * `properties["x"]` is deliberately not matched: that is the Gradle *project*
382
- * property channel — `gradle.properties` and `-P` — which `extractPropertyRefs`
383
- * already reports, and which is not this file.
384
- */
385
- function extractDirectPropertyReads(content) {
386
- const names = [];
387
- const direct = /\.\s*getProperty\s*\(\s*["']([^"']+)["']/g;
388
- let m;
389
- while ((m = direct.exec(content)) !== null)
390
- names.push(m[1]);
391
- for (const accessor of propertiesAccessorNames(content)) {
392
- const call = new RegExp(`\\b${escapeForRegExp(accessor)}\\s*\\(\\s*["']([^"']+)["']`, "g");
393
- let c;
394
- while ((c = call.exec(content)) !== null)
395
- names.push(c[1]);
396
- }
397
- return names;
398
- }
399
- function escapeForRegExp(value) {
400
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
401
- }
402
- /** Functions declared in this file whose body reads the properties file. */
403
- function propertiesAccessorNames(content) {
404
- const names = [];
405
- // Groovy `def name(...) {` and Kotlin `fun name(...) {`, which is how a
406
- // build script declares one.
407
- const decl = /\b(?:def|fun)\s+([A-Za-z_]\w*)\s*\([^)]*\)\s*(?::[^{]*)?\{/g;
408
- let m;
409
- while ((m = decl.exec(content)) !== null) {
410
- // `matchingBrace` counts from inside the block, so hand it the index after
411
- // the opening brace the declaration regex ended on.
412
- const body = m.index + m[0].length;
413
- const close = matchingBrace(content, body);
414
- if (close === -1)
415
- continue;
416
- if (readsPropertiesFileDirectly(content.slice(body, close)))
417
- names.push(m[1]);
418
- }
419
- return names;
420
- }
421
- /**
422
- * Keys that name a location on the machine running the build, not a value the
423
- * build needs supplied.
424
- *
425
- * Writing one into a generated file puts a developer's own SDK path onto a
426
- * runner, where it is wrong — and Gradle finds the SDK from `ANDROID_HOME`
427
- * when the key is absent, which is how every build on a runner resolves it.
428
- * The Secrets plugin ignores `sdk.dir` for the same reason.
429
- */
430
- const MACHINE_LOCAL_KEYS = new Set(["sdk.dir", "ndk.dir", "cmake.dir"]);
431
- // Bounds on the source walk, the way `findGradleFiles` bounds itself to the
432
- // files a build actually declares. A repository is an unbounded tree and this
433
- // runs on every scan of one.
434
- const SOURCE_EXTENSIONS = [".kt", ".kts", ".java"];
435
- const SOURCE_SKIP_DIRS = new Set(["build", "node_modules"]);
436
- /**
437
- * **Manifests get their own budget, and a source walk cannot spend it.**
438
- *
439
- * They shared one at first, with source walked first, and on a real application
440
- * that meant the manifests were never reached at all: 1743 source files against
441
- * a 1500-file cap left nothing, and the keys referenced only as `${KEY}` in a
442
- * manifest — which is most of them — came back missing. A scan that answers
443
- * well on a small repository and silently thins out on a large one is the worse
444
- * of the two failures, because the large one is the one someone is paid to
445
- * build.
446
- *
447
- * There are few manifests and many source files — 47 against 1743 in that same
448
- * application — so the two are not competing for a scarce resource, they were
449
- * just sharing the wrong one.
450
- */
451
- const SOURCE_FILE_CAP = 8000;
452
- const MANIFEST_FILE_CAP = 500;
453
- const SOURCE_DEPTH_CAP = 12;
454
- /** Source files under `dir`, up to the shared file budget. */
455
- function findSourceFiles(dir, budget, depth = 0) {
456
- if (depth > SOURCE_DEPTH_CAP || budget.left <= 0)
457
- return [];
458
- let entries;
459
- try {
460
- entries = readdirSync(dir, { withFileTypes: true });
461
- }
462
- catch {
463
- return [];
464
- }
465
- const files = [];
466
- const subdirs = [];
467
- for (const entry of entries) {
468
- // Dot directories hold caches and VCS state, never source: `.git`,
469
- // `.gradle`, `.idea`, `.kotlin`.
470
- if (entry.name.startsWith("."))
471
- continue;
472
- if (entry.isDirectory()) {
473
- if (!SOURCE_SKIP_DIRS.has(entry.name))
474
- subdirs.push(join(dir, entry.name));
475
- }
476
- else if (SOURCE_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {
477
- if (budget.left <= 0)
478
- break;
479
- budget.left--;
480
- files.push(join(dir, entry.name));
481
- }
482
- }
483
- for (const sub of subdirs)
484
- files.push(...findSourceFiles(sub, budget, depth + 1));
485
- return files;
486
- }
487
- /** The directories of `dirs` that no other member of `dirs` contains. */
488
- function outermost(dirs) {
489
- return dirs.filter((dir) => !dirs.some((other) => other !== dir && dir.startsWith(other + "/")));
490
- }
491
- /** Manifests under `dir`, up to the shared file budget. */
492
- function findManifests(dir, budget, depth = 0) {
493
- if (depth > SOURCE_DEPTH_CAP || budget.left <= 0)
494
- return [];
495
- let entries;
496
- try {
497
- entries = readdirSync(dir, { withFileTypes: true });
498
- }
499
- catch {
500
- return [];
501
- }
502
- const found = [];
503
- for (const entry of entries) {
504
- if (entry.name.startsWith("."))
505
- continue;
506
- if (entry.isDirectory()) {
507
- if (!SOURCE_SKIP_DIRS.has(entry.name)) {
508
- found.push(...findManifests(join(dir, entry.name), budget, depth + 1));
509
- }
510
- }
511
- else if (entry.name === "AndroidManifest.xml") {
512
- if (budget.left <= 0)
513
- break;
514
- budget.left--;
515
- found.push(join(dir, entry.name));
516
- }
517
- }
518
- return found;
519
- }
520
- /**
521
- * The property names the build reads out of the properties file — see
522
- * `ScanResult.missingPropertyKeys`. Four sources, because a repository may use
523
- * any of them and most use exactly one:
524
- *
525
- * - `${KEY}` in an `AndroidManifest.xml`, which the Secrets plugin fills. The
526
- * commonest of the four by a distance, and the one its documentation shows.
527
- * - `manifestPlaceholders` declared in a build file.
528
- * - `BuildConfig.<FIELD>` in source, for a key read as a generated constant.
529
- * - `getProperty("key")` in a build file that loads the properties file
530
- * itself, for a build that does not use the plugin at all.
531
- *
532
- * Bounded to the modules that either apply the plugin or read the file
533
- * directly. A module that does neither cannot be reading a key out of it.
534
- */
535
- function collectPropertyKeys(gradleFiles) {
536
- // A module with both a `build.gradle` and a `build.gradle.kts` yields its
537
- // directory twice, and walking it twice would spend the file budget twice.
538
- const moduleDirs = new Set();
539
- const keys = new Set();
540
- for (const filePath of gradleFiles) {
541
- const content = safeRead(filePath);
542
- const plugin = appliesSecretsPlugin(content);
543
- const direct = readsPropertiesFileDirectly(content);
544
- if (!plugin && !direct)
545
- continue;
546
- moduleDirs.add(dirname(filePath));
547
- for (const key of extractManifestPlaceholderKeys(content))
548
- keys.add(key);
549
- if (direct)
550
- for (const key of extractDirectPropertyReads(content))
551
- keys.add(key);
552
- }
553
- if (moduleDirs.size === 0)
554
- return [];
555
- const sourceBudget = { left: SOURCE_FILE_CAP };
556
- const manifestBudget = { left: MANIFEST_FILE_CAP };
557
- for (const dir of outermost([...moduleDirs])) {
558
- // Manifests first. They are the likeliest place for a key and the cheapest
559
- // to read, so if anything is ever going to run short it must not be these.
560
- for (const manifest of findManifests(dir, manifestBudget)) {
561
- for (const name of extractManifestXmlPlaceholders(safeRead(manifest)))
562
- keys.add(name);
563
- }
564
- for (const file of findSourceFiles(dir, sourceBudget)) {
565
- for (const name of extractBuildConfigFieldRefs(safeRead(file)))
566
- keys.add(name);
567
- }
568
- }
569
- for (const key of MACHINE_LOCAL_KEYS)
570
- keys.delete(key);
571
- return [...keys].sort();
572
- }
573
- /** Parse property keys from a standard .properties file, skipping comments and blank lines. */
574
- function parsePropertyKeys(propertiesContent) {
575
- return propertiesContent
576
- .split("\n")
577
- .map((l) => l.trim())
578
- .filter((l) => l && !l.startsWith("#") && l.includes("="))
579
- .map((l) => l.split("=")[0].trim())
580
- .filter(Boolean);
581
- }
582
- /**
583
- * Removes `if (file("…").exists()) { … }` blocks from build-file content.
584
- *
585
- * A plugin applied inside one of these is *conditional on a file the build
586
- * may not have*. Projects use the idiom precisely so a checkout without the
587
- * credential still builds:
588
- *
589
- * if (file("google-services.json").exists()) {
590
- * apply(plugin = libs.plugins.gms.googleServices.get().pluginId)
591
- * }
592
- *
593
- * Reading the declaration and ignoring the guard turns "this project builds
594
- * fine without a credential" into "this project cannot build without one" —
595
- * a warning that sends the user looking for a file the build never asks for.
596
- *
597
- * Brace matching skips quoted strings so a `{` inside a literal cannot end
598
- * the block early. Anything it cannot match to a close brace is left alone:
599
- * losing a real declaration is worse than keeping a guarded one.
600
- */
601
- export function stripExistenceGuardedBlocks(content) {
602
- const guard = /if\s*\(\s*[A-Za-z0-9_.]*file\s*\(\s*[^)]*\)\s*\.exists\s*\(\s*\)\s*\)\s*\{/g;
603
- let out = "";
604
- let cursor = 0;
605
- let match;
606
- while ((match = guard.exec(content)) !== null) {
607
- const bodyStart = match.index + match[0].length;
608
- const end = matchingBrace(content, bodyStart);
609
- if (end === -1)
610
- continue;
611
- out += content.slice(cursor, match.index);
612
- cursor = end + 1;
613
- guard.lastIndex = cursor;
614
- }
615
- return out + content.slice(cursor);
616
- }
617
- /** Index of the `}` closing a block whose body starts at `start`, or -1. */
618
- function matchingBrace(content, start) {
619
- let depth = 1;
620
- let quote = null;
621
- for (let i = start; i < content.length; i++) {
622
- const ch = content[i];
623
- if (quote) {
624
- if (ch === "\\")
625
- i++;
626
- else if (ch === quote)
627
- quote = null;
628
- continue;
629
- }
630
- if (ch === '"' || ch === "'")
631
- quote = ch;
632
- else if (ch === "{")
633
- depth++;
634
- else if (ch === "}" && --depth === 0)
635
- return i;
636
- }
637
- return -1;
638
- }
639
- /**
640
- * Returns true if the GMS google-services plugin is applied unconditionally
641
- * in this content. A declaration guarded by the presence of
642
- * `google-services.json` is not a credential requirement — see
643
- * `stripExistenceGuardedBlocks`.
644
- */
645
- function detectGmsPlugin(content) {
646
- const unguarded = stripExistenceGuardedBlocks(content);
647
- return (/apply\s+plugin\s*:\s*['"]com\.google\.gms\.google-services['"]/.test(unguarded) ||
648
- /id\s*\(\s*["']com\.google\.gms\.google-services["']\s*\)/.test(unguarded) ||
649
- /id\s*["']com\.google\.gms\.google-services["']/.test(unguarded));
650
- }
651
- /** Returns true if a signingConfigs block exists. */
652
- function detectSigningConfigs(content) {
653
- return /signingConfigs\s*\{/.test(content);
654
- }
655
- /**
656
- * Extract env var and property references from within signingConfigs blocks.
657
- * Looks for storeFile, storePassword, keyAlias, keyPassword assignments.
658
- */
659
- function parseSigningCredentialRefs(content) {
660
- const results = [];
661
- const blockRe = /signingConfigs\s*\{([\s\S]*?)^\s*\}/m;
662
- const match = blockRe.exec(content);
663
- if (!match)
664
- return results;
665
- const block = match[1];
666
- const blockStart = match.index;
667
- // System.getenv inside signing block
668
- const envRe = /System\.getenv\(\s*["']([^"']+)["']\s*\)/g;
669
- let m;
670
- while ((m = envRe.exec(block)) !== null) {
671
- results.push({ name: m[1], line: lineNumber(content, blockStart + m.index) });
672
- }
673
- // findProperty / properties[] inside signing block
674
- const propRe = /(?:project\.)?findProperty\(\s*["']([^"']+)["']\s*\)|(?:project\.)?properties\["([^"]+)"\]/g;
675
- while ((m = propRe.exec(block)) !== null) {
676
- results.push({ name: m[1] ?? m[2], line: lineNumber(content, blockStart + m.index) });
677
- }
678
- return results;
679
- }
680
- /**
681
- * Extract referenced keystore file paths from signingConfigs.
682
- * Handles: storeFile file("path") and storeFile = file("path")
683
- */
684
- function extractKeystorePaths(content) {
685
- const paths = [];
686
- const re = /storeFile\s*(?:=\s*)?file\(\s*["']([^"']+)["']\s*\)/g;
687
- let m;
688
- while ((m = re.exec(content)) !== null) {
689
- paths.push(m[1]);
690
- }
691
- return paths;
692
- }
693
- // ---------------------------------------------------------------------------
694
- // Java version detection
695
- // ---------------------------------------------------------------------------
696
- /**
697
- * Parses a raw version token into a major Java version number.
698
- * Handles formats: "21", "17", "1.8" (→ 8), "VERSION_21", "VERSION_1_8" (→ 8).
699
- */
700
- function parseJavaVersion(raw) {
701
- // `JvmTarget.fromTarget("17")` names the version inside a string.
702
- const fromTarget = /fromTarget\s*\(\s*["']([^"']+)["']/.exec(raw);
703
- if (fromTarget)
704
- return parseJavaVersion(fromTarget[1]);
705
- // JavaVersion enum: VERSION_21, VERSION_17, VERSION_1_8
706
- // JvmTarget enum: JVM_21, JVM_17, JVM_1_8
707
- const enumMatch = /(?:VERSION|JVM)_1_(\d)$/.exec(raw);
708
- if (enumMatch)
709
- return parseInt(enumMatch[1], 10);
710
- const enumMatch2 = /(?:VERSION|JVM)_(\d+)$/.exec(raw);
711
- if (enumMatch2)
712
- return parseInt(enumMatch2[1], 10);
713
- // String literal: "21", "17", "11", "1.8"
714
- const legacyMatch = /^1\.(\d)$/.exec(raw.trim());
715
- if (legacyMatch)
716
- return parseInt(legacyMatch[1], 10);
717
- const plain = parseInt(raw.trim(), 10);
718
- if (!isNaN(plain))
719
- return plain;
720
- return undefined;
721
- }
722
- /**
723
- * Scans Gradle file content for Java / JVM version requirements and returns
724
- * the highest version number found.
725
- *
726
- * Detects:
727
- * - sourceCompatibility / targetCompatibility (Groovy & Kotlin DSL)
728
- * - kotlinOptions { jvmTarget = "21" }
729
- * - jvmToolchain(21) / jvmToolchain { languageVersion.set(JavaLanguageVersion.of(21)) }
730
- * - java { toolchain { languageVersion.set(JavaLanguageVersion.of(21)) } }
731
- */
732
- function detectJavaVersionInContent(content) {
733
- const candidates = [];
734
- // sourceCompatibility / targetCompatibility = JavaVersion.VERSION_21 or = "21" or = JavaVersion.VERSION_1_8
735
- const compatRe = /(?:sourceCompatibility|targetCompatibility)\s*[=\s]+([A-Za-z0-9._]+)/g;
736
- let m;
737
- while ((m = compatRe.exec(content)) !== null) {
738
- const v = parseJavaVersion(m[1]);
739
- if (v)
740
- candidates.push(v);
741
- }
742
- // jvmTarget, in every form a Kotlin build writes it:
743
- // jvmTarget = "21" (kotlinOptions, Groovy)
744
- // jvmTarget.set(JvmTarget.JVM_17) (compilerOptions, the form
745
- // LinuxCommandLibrary uses
746
- // everywhere and we read in
747
- // none of them)
748
- // jvmTarget = JvmTarget.JVM_21
749
- // jvmTarget.set(JvmTarget.fromTarget("17"))
750
- const jvmTargetFromTargetRe = /jvmTarget\s*(?:=|\.set\s*\(\s*)\s*JvmTarget\.fromTarget\s*\(\s*["']([^"']+)["']/g;
751
- while ((m = jvmTargetFromTargetRe.exec(content)) !== null) {
752
- const v = parseJavaVersion(m[1]);
753
- if (v)
754
- candidates.push(v);
755
- }
756
- const jvmTargetRe = /jvmTarget\s*(?:=|\.set\s*\(\s*)\s*(?:JvmTarget\.)?["']?([A-Za-z0-9._]+)["']?/g;
757
- while ((m = jvmTargetRe.exec(content)) !== null) {
758
- const v = parseJavaVersion(m[1]);
759
- if (v)
760
- candidates.push(v);
761
- }
762
- // jvmToolchain(21)
763
- const toolchainRe = /jvmToolchain\s*\(\s*(\d+)\s*\)/g;
764
- while ((m = toolchainRe.exec(content)) !== null) {
765
- candidates.push(parseInt(m[1], 10));
766
- }
767
- // JavaLanguageVersion.of(21)
768
- const langVerRe = /JavaLanguageVersion\.of\s*\(\s*(\d+)\s*\)/g;
769
- while ((m = langVerRe.exec(content)) !== null) {
770
- candidates.push(parseInt(m[1], 10));
771
- }
772
- return candidates.length > 0 ? Math.max(...candidates) : undefined;
773
- }
774
- /** The builds a settings file composes in, e.g. `includeBuild("build-logic")`. */
775
- export function parseIncludedBuilds(content) {
776
- if (!content || typeof content !== "string")
777
- return [];
778
- const builds = [];
779
- for (const m of content.matchAll(/includeBuild\s*\(?\s*["']([^"']+)["']/g)) {
780
- if (!builds.includes(m[1]))
781
- builds.push(m[1]);
782
- }
783
- return builds;
784
- }
785
- /**
786
- * A Java version pinned outside Gradle, and the file that pins it.
787
- *
788
- * `mihonapp/mihon` declares no Java level in any Gradle file and pins 21 in
789
- * `.github/.java-version`, which its own workflows consume through
790
- * `setup-java`'s `java-version-file:`. The project says what it needs; it
791
- * just does not say it in Gradle, and reading only Gradle files scanned it as
792
- * "nothing declared" and ran it on the default. Across the thirty clones,
793
- * three repositories pin with a version file and ten name a version in a
794
- * workflow.
795
- *
796
- * A pin is a **floor**, folded in with `Math.max` like every other
797
- * declaration — not an override.
798
- */
799
- export function detectPinnedJavaVersion(root) {
800
- const found = [];
801
- const offer = (version, source) => {
802
- if (version !== undefined && version > 0)
803
- found.push({ version, source });
804
- };
805
- for (const name of [".java-version", ".github/.java-version"]) {
806
- const content = safeRead(resolve(root, name));
807
- if (content)
808
- offer(parseJavaVersion(content.trim().split("\n")[0] ?? ""), name);
809
- }
810
- // asdf / mise: `java temurin-21.0.3+9` — the major version is what matters.
811
- const toolVersions = safeRead(resolve(root, ".tool-versions"));
812
- if (toolVersions) {
813
- const m = /^\s*java\s+(\S+)/m.exec(toolVersions);
814
- if (m)
815
- offer(parseJavaVersion(m[1].replace(/^[A-Za-z-]+-/, "")), ".tool-versions");
816
- }
817
- // SDKMAN: `java=17.0.9-tem`
818
- const sdkmanrc = safeRead(resolve(root, ".sdkmanrc"));
819
- if (sdkmanrc) {
820
- const m = /^\s*java\s*=\s*(\S+)/m.exec(sdkmanrc);
821
- if (m)
822
- offer(parseJavaVersion(m[1]), ".sdkmanrc");
823
- }
824
- for (const workflow of listWorkflowFiles(root)) {
825
- const content = safeRead(workflow);
826
- if (!content)
827
- continue;
828
- const name = relative(root, workflow);
829
- // `java-version: 21`, `java-version: '21'`, and a matrix list — take the
830
- // highest, because that is the one that has to work.
831
- for (const m of content.matchAll(/java-version\s*:\s*(.+)/g)) {
832
- for (const token of m[1].split(/[,[\]\s]+/)) {
833
- const cleaned = token.replace(/["']/g, "").trim();
834
- if (cleaned)
835
- offer(parseJavaVersion(cleaned), name);
836
- }
837
- }
838
- // `java-version-file: .github/.java-version` — read what it points at.
839
- for (const m of content.matchAll(/java-version-file\s*:\s*["']?([^"'\s]+)/g)) {
840
- const pinned = safeRead(resolve(root, m[1]));
841
- if (pinned)
842
- offer(parseJavaVersion(pinned.trim().split("\n")[0] ?? ""), m[1]);
843
- }
844
- }
845
- if (found.length === 0)
846
- return undefined;
847
- return found.reduce((a, b) => (b.version > a.version ? b : a));
848
- }
849
- function listWorkflowFiles(root) {
850
- const dir = resolve(root, ".github", "workflows");
851
- try {
852
- return readdirSync(dir)
853
- .filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"))
854
- .map((f) => resolve(dir, f));
855
- }
856
- catch {
857
- return [];
858
- }
859
- }
860
- /**
861
- * Returns the highest Java version requirement found across all Gradle files,
862
- * or undefined if none is detected.
863
- */
864
- function detectRequiredJavaVersion(gradleFiles) {
865
- const versions = [];
866
- for (const filePath of gradleFiles) {
867
- const content = safeRead(filePath);
868
- const v = detectJavaVersionInContent(content);
869
- if (v !== undefined)
870
- versions.push(v);
871
- }
872
- return versions.length > 0 ? Math.max(...versions) : undefined;
873
- }
874
- /**
875
- * The Gradle version a wrapper pins, read out of its `distributionUrl`.
876
- * `…/gradle-8.9-bin.zip` → `8.9`.
877
- *
878
- * This bounds which JDK the project can build on: Gradle refuses to start on
879
- * a JDK newer than it knows about, so the wrapper is a hard ceiling that no
880
- * `sourceCompatibility` declaration can raise.
881
- *
882
- * The trailing `-bin` / `-all` belongs to the file name rather than the
883
- * version, and a release candidate spends two more hyphens on `-rc-1`, so the
884
- * version group has to be able to give its last segment back.
885
- */
886
- export function parseGradleWrapperVersion(content) {
887
- const match = /gradle-(\d+(?:\.\d+)*(?:-[A-Za-z0-9.]+)*)-(?:bin|all)\.zip/.exec(content);
888
- return match ? match[1] : undefined;
889
- }
890
- function detectGradleWrapperVersion(projectRoot) {
891
- const path = resolve(projectRoot, "gradle", "wrapper", "gradle-wrapper.properties");
892
- if (!existsSync(path))
893
- return undefined;
894
- return parseGradleWrapperVersion(safeRead(path));
895
- }
896
- /**
897
- * The newest Java release a given Gradle version can run on, or undefined
898
- * when the version string says nothing usable.
899
- */
900
- export function gradleJavaCeiling(gradleVersion) {
901
- const parsed = /^(\d+)(?:\.(\d+))?/.exec(gradleVersion ?? "");
902
- const major = parsed ? Number(parsed[1]) : NaN;
903
- if (!Number.isInteger(major))
904
- return undefined;
905
- const minor = Number(parsed?.[2] ?? 0);
906
- const atLeast = (m, n) => major > m || (major === m && minor >= n);
907
- // Minor-aware, because the boundaries are minor releases. While 17 was the
908
- // preferred JDK this was harmless — the ceiling only had to be right about
909
- // whether 17 fitted. Preferring the newest makes it load-bearing: JDK 21
910
- // arrived in Gradle 8.5, so 8.0 through 8.4 answering "21" would hand those
911
- // projects a JDK their own wrapper refuses to start on.
912
- // Gradle 9.1 added JDK 25; 9.0 tops out at 24. Without this the six corpus
913
- // repositories declaring a level above 21 would still abstain, because a
914
- // floor of 25 against a ceiling of 24 intersects with nothing — installing
915
- // JDK 25 buys nothing if no ceiling ever reaches it.
916
- if (atLeast(9, 1))
917
- return 25;
918
- if (major >= 9)
919
- return 24;
920
- if (atLeast(8, 5))
921
- return 21;
922
- if (atLeast(7, 3))
923
- return 17;
924
- if (atLeast(5, 0))
925
- return 11;
926
- return 8;
927
- }
928
- /** The oldest Java release a given Gradle version will start on. */
929
- export function gradleJavaFloor(gradleVersion) {
930
- const major = Number(/^(\d+)\./.exec(gradleVersion ?? "")?.[1]);
931
- if (!Number.isInteger(major))
932
- return undefined;
933
- return major >= 9 ? 17 : 8;
934
- }
935
- /**
936
- * The JDKs a build machine installs, and therefore the only values the
937
- * generated `set-java-version` step can satisfy.
938
- *
939
- * 11 joined the images on 2026-09-05, for Gradle 5.x and 6.x — which cannot
940
- * *start* on 17, whatever the sources declare. It is a fallback, never a
941
- * preference: the selection takes the newest JDK that fits, so 11 is reached
942
- * only when a wrapper's ceiling leaves nothing else.
943
- *
944
- * 8 and 25 joined on 2026-09-07, the companion half of the "newest the
945
- * wrapper allows" decision — the images carry every JDK we find a project
946
- * asking for, rather than a project bending to what they carry. 8 is 11's
947
- * argument one line further back (Gradle 4.x tops out at Java 8); 25 covers
948
- * the six repositories in the 150-repo corpus that declare a level above 21.
949
- * Nothing reaches 8 unless a wrapper ceiling forces it.
950
- */
951
- export const AVAILABLE_JAVA_VERSIONS = [8, 11, 17, 21, 25];
952
- /**
953
- * The newest JDK a build machine carries.
954
- *
955
- * It is no longer what the chooser falls back to. It used to be: with no JDK
956
- * satisfying both the sources and the wrapper, `runnableJavaVersion` returned
957
- * this — which on a project pinning a modern Java level under an old Gradle
958
- * wrapper is a JDK **above** that wrapper's ceiling, and the build dies at
959
- * `:app:processDebugMainManifest` with "module java.base does not opens
960
- * java.io to unnamed module". A number above the ceiling is not a fallback;
961
- * it is the failure, chosen deliberately and reported as success.
962
- */
963
- export const NEWEST_AVAILABLE_JAVA_VERSION = Math.max(...AVAILABLE_JAVA_VERSIONS);
964
- /**
965
- * The JDK to build on, with the reason attached — one place where the
966
- * constraints are resolved, so the answer and the explanation cannot disagree.
967
- *
968
- * They used to. The chooser fell back to the newest installed JDK whenever
969
- * nothing satisfied both constraints, and the sentence beside it had two
970
- * branches — a ceiling below every installed JDK, and a declaration above
971
- * every installed JDK — so the commonest shape of all fell through both and
972
- * said nothing at all. A project pinning Java 12 against a Gradle wrapper that
973
- * runs on 11 at most got Java 25 and silence.
974
- *
975
- * Three facts decide it:
976
- *
977
- * - **What the build machine has.** Only `AVAILABLE_JAVA_VERSIONS` exist, so
978
- * a project declaring 30 cannot simply be given 30.
979
- * - **What Gradle can run on.** The wrapper sets a ceiling that source
980
- * compatibility cannot raise: Gradle starts before it reads a single
981
- * `sourceCompatibility`.
982
- * - **What the sources ask for.** A declaration is a floor, not an exact
983
- * request — a project declaring 11 compiles fine on 21.
984
- *
985
- * `kind` distinguishes the five outcomes:
986
- *
987
- * - `unknown` — nothing declared and no readable wrapper. Nothing to go on
988
- * and nothing to say.
989
- * - `satisfies` — an installed JDK meets both the floor and the ceiling.
990
- * - `capped-by-wrapper` — nothing meets both, and the wrapper is why. The
991
- * ceiling is hard and the declaration is a preference, so the newest JDK
992
- * the wrapper *can* run is the answer, and the caller says so.
993
- * - `above-runners` — the declaration is above every installed JDK, and that
994
- * rather than the wrapper is what binds. **No answer**: asking for it would
995
- * fail when the JDK is selected, and quietly picking a lower one would be
996
- * the guess this exists to avoid.
997
- * - `wrapper-below-runners` — the ceiling is below every installed JDK. No
998
- * answer exists.
999
- *
1000
- * `above-runners` is checked before `capped-by-wrapper` so a project declaring
1001
- * 99 against a modern wrapper is told about the machines rather than about its
1002
- * wrapper: both bind, and the one it can act on is the one worth naming.
1003
- */
1004
- export function chooseJavaVersion(detectedJavaVersion, gradleVersion) {
1005
- const declared = Number(detectedJavaVersion) || 0;
1006
- const ceiling = gradleJavaCeiling(gradleVersion);
1007
- const floor = gradleJavaFloor(gradleVersion);
1008
- const installed = [...AVAILABLE_JAVA_VERSIONS].sort((a, b) => a - b);
1009
- const base = { declared, ceiling, installed };
1010
- if (!declared && floor === undefined) {
1011
- return { ...base, version: undefined, kind: "unknown" };
1012
- }
1013
- const wanted = Math.max(declared, floor ?? 0);
1014
- const usable = installed.filter((v) => v >= wanted && v <= (ceiling ?? Infinity));
1015
- // The newest that fits. Nothing reaches the oldest installed JDK unless a
1016
- // ceiling leaves nothing else, which is the only reason 8 and 11 are on the
1017
- // images at all.
1018
- if (usable.length > 0) {
1019
- return { ...base, version: usable[usable.length - 1], kind: "satisfies" };
1020
- }
1021
- if (declared > installed[installed.length - 1]) {
1022
- return { ...base, version: undefined, kind: "above-runners" };
1023
- }
1024
- const underCeiling = installed.filter((v) => v <= (ceiling ?? Infinity));
1025
- if (underCeiling.length > 0) {
1026
- return {
1027
- ...base,
1028
- version: underCeiling[underCeiling.length - 1],
1029
- kind: "capped-by-wrapper",
1030
- };
1031
- }
1032
- return { ...base, version: undefined, kind: "wrapper-below-runners" };
1033
- }
1034
- /**
1035
- * The Java version to write into a generated pipeline: the **newest**
1036
- * available JDK that satisfies both what the sources declare and what the
1037
- * Gradle wrapper can run on.
1038
- *
1039
- * DECIDED 2026-09-06 (Alex), reversing Invarn PRD decision #15 ("17 is the
1040
- * JDK whenever nothing forces otherwise"). The thirty-app study measured the
1041
- * cost of preferring 17 in one direction and none in the other: two rows
1042
- * failed because 17 was too *low* for a build plugin's own runtime
1043
- * requirement, and no row failed because 21 was too high. A project targeting
1044
- * 17 runs fine on 21; never the reverse.
1045
- *
1046
- * Source compatibility is a floor, not an exact request. Gradle starts before
1047
- * it reads a single `sourceCompatibility`, so a wrapper pinning an old Gradle
1048
- * caps the JDK no matter what the sources say — which is why the ceiling
1049
- * above had to learn minor versions before this could ship.
1050
- */
1051
- export function runnableJavaVersion(detectedJavaVersion, gradleVersion) {
1052
- return chooseJavaVersion(detectedJavaVersion, gradleVersion).version;
1053
- }
1054
- /**
1055
- * What the choice could not honour, as a sentence, or undefined when it
1056
- * honoured everything.
1057
- *
1058
- * Silence is the failure this exists to prevent: a pipeline that quietly
1059
- * overruled what a project asked for reads as the project's own problem, and
1060
- * the commonest shape of all — an old wrapper under a modern declaration — was
1061
- * the one that used to fall through every branch.
1062
- *
1063
- * `source` names the file that asked, when the caller knows it. The scan now
1064
- * returns it, so "asks for Java 12" can become "asks for Java 12
1065
- * (.github/workflows/Build.yml)" — a version with no provenance sends a reader
1066
- * looking through every Gradle file for a number that is not in any of them.
1067
- */
1068
- export function javaVersionProblem(detectedJavaVersion, gradleVersion, source) {
1069
- const choice = chooseJavaVersion(detectedJavaVersion, gradleVersion);
1070
- if (choice.kind === "satisfies" || choice.kind === "unknown")
1071
- return undefined;
1072
- const { declared, ceiling, installed } = choice;
1073
- // "11, 17 and 21", not "11 and 17 and 21" — a human reads this next to a
1074
- // build that will not start.
1075
- const list = installed.length > 1
1076
- ? `${installed.slice(0, -1).join(", ")} and ${installed[installed.length - 1]}`
1077
- : String(installed[0]);
1078
- const asked = source ? `Java ${declared} (${source})` : `Java ${declared}`;
1079
- if (choice.kind === "wrapper-below-runners") {
1080
- return `Gradle ${gradleVersion} runs on Java ${ceiling} at most, and build machines carry only ${list}. This pipeline will very likely fail before it compiles anything — upgrade the Gradle wrapper.`;
1081
- }
1082
- if (choice.kind === "above-runners") {
1083
- return `This project declares Java ${declared}, and build machines carry only ${list}. Leaving JAVA_VERSION at the pipeline default — asking for ${declared} would fail at "Set the Java version", and quietly choosing a lower one would be a guess.`;
1084
- }
1085
- // capped-by-wrapper
1086
- const preamble = declared
1087
- ? `This project asks for ${asked}, and Gradle ${gradleVersion} runs on Java ${ceiling} at most.`
1088
- : `Gradle ${gradleVersion} runs on Java ${ceiling} at most.`;
1089
- return `${preamble} Building on ${choice.version} — the newest the wrapper allows.`;
1090
- }
1091
- // ---------------------------------------------------------------------------
1092
- // Gitignore helper
1093
- // ---------------------------------------------------------------------------
1094
- const CI_RELEVANT_GITIGNORE_PATTERNS = [
1095
- { pattern: /google-services\.json/, label: "google-services.json" },
1096
- { pattern: /local\.properties/, label: "local.properties" },
1097
- { pattern: /keystore\.properties/, label: "keystore.properties" },
1098
- { pattern: /\.jks$|\.keystore$|\*\.jks|\*\.keystore/, label: "keystore files (*.jks / *.keystore)" },
1099
- { pattern: /\.env$|\*\.env/, label: ".env files" },
1100
- ];
1101
- function checkGitignore(root) {
1102
- const gitignorePath = resolve(root, ".gitignore");
1103
- if (!existsSync(gitignorePath))
1104
- return [];
1105
- const content = safeRead(gitignorePath);
1106
- const lines = content.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
1107
- const found = [];
1108
- for (const { pattern, label } of CI_RELEVANT_GITIGNORE_PATTERNS) {
1109
- if (lines.some((l) => pattern.test(l))) {
1110
- found.push(label);
1111
- }
1112
- }
1113
- return found;
1114
- }
1115
- // ---------------------------------------------------------------------------
1116
- // Build variant detection
1117
- // ---------------------------------------------------------------------------
1118
- /**
1119
- * Extracts the inner content of a named block using brace-counting.
1120
- * e.g. extractBlockContent(content, "buildTypes") returns everything inside buildTypes { ... }.
1121
- * Returns undefined if the block is not found.
1122
- */
1123
- function extractBlockContent(content, keyword) {
1124
- const keywordRe = new RegExp(`\\b${keyword}\\s*\\{`);
1125
- const match = keywordRe.exec(content);
1126
- if (!match)
1127
- return undefined;
1128
- const start = content.indexOf("{", match.index);
1129
- let depth = 0;
1130
- for (let i = start; i < content.length; i++) {
1131
- if (content[i] === "{")
1132
- depth++;
1133
- else if (content[i] === "}") {
1134
- depth--;
1135
- if (depth === 0)
1136
- return content.slice(start + 1, i);
1137
- }
1138
- }
1139
- return undefined;
1140
- }
1141
- const CONTROL_KEYWORDS = new Set([
1142
- "if", "else", "for", "while", "when", "try", "catch", "finally",
1143
- "return", "val", "var", "fun", "object", "class", "interface",
1144
- ]);
1145
- /**
1146
- * Extracts the names of direct child blocks from block content.
1147
- * Handles both Groovy DSL (name { }) and Kotlin DSL (create("name") { } / getByName("name") { }).
1148
- */
1149
- function extractDirectChildNames(block) {
1150
- const names = [];
1151
- let depth = 0;
1152
- for (const line of block.split("\n")) {
1153
- const opens = (line.match(/\{/g) || []).length;
1154
- const closes = (line.match(/\}/g) || []).length;
1155
- if (depth === 0 && opens > 0) {
1156
- // Kotlin DSL: create("name") { or getByName("name") {
1157
- const kotlinMatch = /(?:create|getByName)\(\s*["'](\w+)["']\s*\)/.exec(line);
1158
- if (kotlinMatch) {
1159
- names.push(kotlinMatch[1]);
1160
- }
1161
- else {
1162
- // Groovy DSL: name { (identifier followed by optional whitespace then {)
1163
- const groovyMatch = /^\s*([a-z]\w*)\s*(?:\.\w+\s*)?\{/.exec(line);
1164
- if (groovyMatch && !CONTROL_KEYWORDS.has(groovyMatch[1])) {
1165
- names.push(groovyMatch[1]);
1166
- }
1167
- }
1168
- }
1169
- depth += opens - closes;
1170
- if (depth < 0)
1171
- depth = 0;
1172
- }
1173
- return [...new Set(names)];
1174
- }
1175
- /** Source-set directories AGP defines itself, which never name a flavor. */
1176
- const SHARED_SOURCE_SETS = new Set(["main", "test", "androidTest", "testFixtures"]);
1177
- /** `testProd` and `androidTestProd` are `prod`'s test source sets, not flavors. */
1178
- const TEST_SOURCE_SET_PREFIXES = ["test", "androidTest"];
1179
- /**
1180
- * What a source set holds. A directory under `src/` carrying none of these is
1181
- * something a human left there, not a source set AGP would read.
1182
- */
1183
- const SOURCE_SET_CONTENTS = new Set([
1184
- "java",
1185
- "kotlin",
1186
- "res",
1187
- "resources",
1188
- "assets",
1189
- "aidl",
1190
- "jni",
1191
- "jniLibs",
1192
- "rs",
1193
- "renderscript",
1194
- "shaders",
1195
- "ml",
1196
- "baselineProfiles",
1197
- "AndroidManifest.xml",
1198
- "google-services.json",
1199
- ]);
1200
- function safeReaddir(dir) {
1201
- try {
1202
- return readdirSync(dir, { withFileTypes: true }).map((e) => ({
1203
- name: e.name,
1204
- isDirectory: e.isDirectory(),
1205
- }));
1206
- }
1207
- catch {
1208
- return [];
1209
- }
1210
- }
1211
- /** `testProd` starts with the segment `test`; a flavor called `testing` does not. */
1212
- function startsWithSegment(name, segment) {
1213
- if (!name.startsWith(segment) || name.length === segment.length)
1214
- return false;
1215
- const next = name[segment.length];
1216
- return next === next.toUpperCase() && next !== next.toLowerCase();
1217
- }
1218
- /** `ciDynamicDebug` ends with the segment `debug`; `staging` does not. */
1219
- function endsWithSegment(name, segment) {
1220
- const capitalized = segment.charAt(0).toUpperCase() + segment.slice(1);
1221
- return name.length > capitalized.length && name.endsWith(capitalized);
1222
- }
1223
- /**
1224
- * Product flavors read from the source sets a module has on disk.
1225
- *
1226
- * Modern Android declares its flavors in a convention plugin — an enum in
1227
- * Kotlin source, applied through `create(flavor.name)` — so there is no
1228
- * literal `productFlavors { … }` block anywhere to parse. The scan is static
1229
- * by design (running a Gradle configuration phase to list flavors would cost
1230
- * minutes on every `ci build`), so the evidence has to be something already on
1231
- * disk. A flavor has a source set, which is the same evidence `invarn setup`
1232
- * has always resolved `google-services.json` by.
1233
- *
1234
- * A directory under `<module>/src/` names a flavor when it is none of the
1235
- * things AGP creates for its own reasons: the shared and test source sets, a
1236
- * build type, a test source set for one flavor, or a variant — a flavor and a
1237
- * build type already joined, which taking for a flavor would cross with the
1238
- * build types a second time and offer `ciDynamicDebugDebug`.
1239
- */
1240
- function flavorsFromSourceSets(gradleFiles, buildTypes) {
1241
- const found = new Set();
1242
- for (const filePath of gradleFiles) {
1243
- const srcDir = join(dirname(filePath), "src");
1244
- for (const entry of safeReaddir(srcDir)) {
1245
- if (!entry.isDirectory)
1246
- continue;
1247
- const name = entry.name;
1248
- if (SHARED_SOURCE_SETS.has(name))
1249
- continue;
1250
- if (buildTypes.includes(name))
1251
- continue;
1252
- if (TEST_SOURCE_SET_PREFIXES.some((p) => startsWithSegment(name, p)))
1253
- continue;
1254
- if (buildTypes.some((bt) => endsWithSegment(name, bt)))
1255
- continue;
1256
- const holds = safeReaddir(join(srcDir, name));
1257
- if (!holds.some((child) => SOURCE_SET_CONTENTS.has(child.name)))
1258
- continue;
1259
- found.add(name);
1260
- }
1261
- }
1262
- // The filesystem's order is not a declaration order, so give it one.
1263
- return Array.from(found).sort();
1264
- }
1265
- /**
1266
- * Detects build types and product flavors from Gradle files by static parsing,
1267
- * falling back to the module's source sets for the flavors when no Gradle file
1268
- * declares any (see `flavorsFromSourceSets`).
1269
- *
1270
- * Always includes "debug" and "release" as defaults.
1271
- *
1272
- * The fallback only ever runs when the literal scan found nothing, so a
1273
- * project whose Gradle files declare flavors is decided by those files exactly
1274
- * as before. Merging the two would let a leftover directory invent a flavor
1275
- * the build does not have.
1276
- */
1277
- export function detectBuildVariants(gradleFiles) {
1278
- const buildTypeSet = new Set(["debug", "release"]);
1279
- const productFlavorSet = new Set();
1280
- for (const filePath of gradleFiles) {
1281
- const content = safeRead(filePath);
1282
- const buildTypesBlock = extractBlockContent(content, "buildTypes");
1283
- if (buildTypesBlock) {
1284
- for (const name of extractDirectChildNames(buildTypesBlock)) {
1285
- buildTypeSet.add(name);
1286
- }
1287
- }
1288
- const productFlavorsBlock = extractBlockContent(content, "productFlavors");
1289
- if (productFlavorsBlock) {
1290
- for (const name of extractDirectChildNames(productFlavorsBlock)) {
1291
- productFlavorSet.add(name);
1292
- }
1293
- }
1294
- }
1295
- const buildTypes = Array.from(buildTypeSet);
1296
- const productFlavors = productFlavorSet.size > 0
1297
- ? Array.from(productFlavorSet)
1298
- : flavorsFromSourceSets(gradleFiles, buildTypes);
1299
- const variants = productFlavors.length === 0
1300
- ? buildTypes
1301
- : productFlavors.flatMap((flavor) => buildTypes.map((bt) => `${flavor}${bt.charAt(0).toUpperCase()}${bt.slice(1)}`));
1302
- return { buildTypes, productFlavors, variants };
1303
- }
1304
- // ---------------------------------------------------------------------------
1305
- // Main scanner
1306
- // ---------------------------------------------------------------------------
1307
- export async function scanAndroidProject(projectRoot) {
1308
- const warnings = [];
1309
- // Track env vars we've already warned about to avoid duplicates
1310
- const seenEnvVars = new Set();
1311
- const seenProperties = new Set();
1312
- // Compute gitignore items upfront — used in multiple sections below
1313
- const gitignoreItems = checkGitignore(projectRoot);
1314
- // ------------------------------------------------------------------
1315
- // 1. Missing files
1316
- // ------------------------------------------------------------------
1317
- if (!existsSync(resolve(projectRoot, "local.properties"))) {
1318
- warnings.push({
1319
- category: "missing-file",
1320
- severity: "warning",
1321
- message: "local.properties not found",
1322
- hint: "CI needs ANDROID_HOME set as an env var, or sdk.dir in local.properties",
1323
- });
1324
- }
1325
- // ------------------------------------------------------------------
1326
- // 2. Detect required Java version and build variants
1327
- // ------------------------------------------------------------------
1328
- const gradleFiles = findGradleFiles(projectRoot);
1329
- const declaredJavaVersion = detectRequiredJavaVersion(gradleFiles);
1330
- // A version file or a workflow pin is a floor like any declaration, and for
1331
- // a project that declares nothing in Gradle it is the only thing that says
1332
- // which JDK the build needs.
1333
- const pinnedJava = detectPinnedJavaVersion(projectRoot);
1334
- const detectedJavaVersion = declaredJavaVersion === undefined && pinnedJava === undefined
1335
- ? undefined
1336
- : Math.max(declaredJavaVersion ?? 0, pinnedJava?.version ?? 0);
1337
- // Only when the pin is what set the level. A Gradle file asking for as much
1338
- // or more is the thing a reader would find by looking, so naming the
1339
- // workflow there would point away from the answer.
1340
- const detectedJavaVersionSource = pinnedJava !== undefined && pinnedJava.version > (declaredJavaVersion ?? 0)
1341
- ? pinnedJava.source
1342
- : undefined;
1343
- const detectedGradleVersion = detectGradleWrapperVersion(projectRoot);
1344
- const buildVariants = detectBuildVariants(gradleFiles);
1345
- // ------------------------------------------------------------------
1346
- // 3. Scan Gradle files
1347
- // ------------------------------------------------------------------
1348
- let gmsDetected = false;
1349
- let gmsDetectedIn = "";
1350
- let signingDetected = false;
1351
- let signingDetectedIn = "";
1352
- for (const filePath of gradleFiles) {
1353
- const content = safeRead(filePath);
1354
- const rel = relPath(projectRoot, filePath);
1355
- // GMS plugin
1356
- if (detectGmsPlugin(content)) {
1357
- gmsDetected = true;
1358
- gmsDetectedIn = rel;
1359
- }
1360
- // Signing config
1361
- if (detectSigningConfigs(content)) {
1362
- signingDetected = true;
1363
- signingDetectedIn = rel;
1364
- // Keystore file paths referenced
1365
- for (const kPath of extractKeystorePaths(content)) {
1366
- const resolved = resolve(projectRoot, kPath);
1367
- if (!existsSync(resolved)) {
1368
- warnings.push({
1369
- category: "missing-file",
1370
- severity: "warning",
1371
- message: `Keystore file not found: ${kPath}`,
1372
- hint: "Provide the keystore file as a secret file in CI or update the path in signingConfigs",
1373
- location: rel,
1374
- });
1375
- }
1376
- }
1377
- // Signing credential env var / property refs
1378
- for (const ref of parseSigningCredentialRefs(content)) {
1379
- if (!seenEnvVars.has(ref.name)) {
1380
- seenEnvVars.add(ref.name);
1381
- warnings.push({
1382
- category: "signing-config",
1383
- severity: "warning",
1384
- message: `Signing credential referenced: ${ref.name}`,
1385
- hint: `Add via: ci secrets add ${ref.name}`,
1386
- location: `${rel}:${ref.line}`,
1387
- });
1388
- }
1389
- }
1390
- }
1391
- // System.getenv() references (outside buildConfigField — caught separately)
1392
- for (const ref of extractEnvVarRefs(content)) {
1393
- if (!seenEnvVars.has(ref.name)) {
1394
- seenEnvVars.add(ref.name);
1395
- warnings.push({
1396
- category: "env-var",
1397
- severity: "warning",
1398
- message: `Environment variable referenced: ${ref.name}`,
1399
- hint: `Add via: ci secrets add ${ref.name}`,
1400
- location: `${rel}:${ref.line}`,
1401
- });
1402
- }
1403
- }
1404
- // buildConfigField + System.getenv references
1405
- for (const ref of extractBuildConfigEnvRefs(content)) {
1406
- if (!seenEnvVars.has(ref.name)) {
1407
- seenEnvVars.add(ref.name);
1408
- warnings.push({
1409
- category: "build-config",
1410
- severity: "warning",
1411
- message: `BuildConfig env var referenced: ${ref.name}`,
1412
- hint: `Add via: ci secrets add ${ref.name}`,
1413
- location: `${rel}:${ref.line}`,
1414
- });
1415
- }
1416
- }
1417
- // Gradle property references
1418
- for (const ref of extractPropertyRefs(content)) {
1419
- if (!seenProperties.has(ref.name)) {
1420
- seenProperties.add(ref.name);
1421
- warnings.push({
1422
- category: "gradle-property",
1423
- severity: "warning",
1424
- message: `Gradle property referenced: ${ref.name}`,
1425
- hint: "Define in gradle.properties or pass as -P flag on CI",
1426
- location: `${rel}:${ref.line}`,
1427
- });
1428
- }
1429
- }
1430
- }
1431
- // ------------------------------------------------------------------
1432
- // 4. Google Secrets Gradle Plugin — detect missing local.properties
1433
- // ------------------------------------------------------------------
1434
- //
1435
- // The Secrets plugin reads a properties file (default: local.properties) and
1436
- // auto-generates BuildConfig fields. If that file is gitignored / absent in CI
1437
- // the fields are not generated, causing "Unresolved reference" compile errors.
1438
- // We check once per unique properties file path across all modules.
1439
- // ------------------------------------------------------------------
1440
- const seenSecretsPropertiesFiles = new Set();
1441
- // First pass: collect the defaultPropertiesFileName from any build file that sets it.
1442
- // This is typically "secrets.defaults.properties" in the app module.
1443
- let secretsDefaultsFileName;
1444
- for (const filePath of gradleFiles) {
1445
- const content = safeRead(filePath);
1446
- const block = extractBlockContent(content, "secrets");
1447
- if (!block)
1448
- continue;
1449
- const m = /defaultPropertiesFileName\s*=\s*["']([^"']+)["']/.exec(block);
1450
- if (m) {
1451
- secretsDefaultsFileName = m[1];
1452
- break;
1453
- }
1454
- }
1455
- // Also look for any *.defaults.properties file in the project root as a fallback
1456
- if (!secretsDefaultsFileName) {
1457
- const candidates = ["secrets.defaults.properties", "local.defaults.properties"];
1458
- for (const c of candidates) {
1459
- if (existsSync(resolve(projectRoot, c))) {
1460
- secretsDefaultsFileName = c;
1461
- break;
1462
- }
1463
- }
1464
- }
1465
- for (const filePath of gradleFiles) {
1466
- const content = safeRead(filePath);
1467
- if (!detectSecretsPlugin(content))
1468
- continue;
1469
- const propsFileName = extractSecretsPropertiesFile(content);
1470
- if (seenSecretsPropertiesFiles.has(propsFileName))
1471
- continue;
1472
- seenSecretsPropertiesFiles.add(propsFileName);
1473
- // Warn when the properties file is gitignored — it won't be present on a clean CI machine
1474
- // even if it exists locally (e.g. local.properties with only sdk.dir).
1475
- const isGitignored = gitignoreItems.some((item) => item.includes(propsFileName));
1476
- if (!isGitignored)
1477
- continue;
1478
- // Find expected keys from the companion defaults file
1479
- const expectedKeys = secretsDefaultsFileName
1480
- ? parsePropertyKeys(safeRead(resolve(projectRoot, secretsDefaultsFileName)))
1481
- : [];
1482
- if (expectedKeys.length > 0) {
1483
- // Emit one warning per expected key so it shows up in the missing-secrets hint
1484
- for (const key of expectedKeys) {
1485
- warnings.push({
1486
- category: "secrets-plugin",
1487
- severity: "warning",
1488
- message: `Secrets plugin key: ${key}`,
1489
- hint: `Add via: ci secrets add ${key}`,
1490
- location: propsFileName,
1491
- });
1492
- }
1493
- }
1494
- else {
1495
- warnings.push({
1496
- category: "secrets-plugin",
1497
- severity: "warning",
1498
- message: `Google Secrets Gradle Plugin reads '${propsFileName}' which is absent in CI`,
1499
- hint: `Inject '${propsFileName}' as a file step using a secret variable`,
1500
- location: relPath(projectRoot, filePath),
1501
- });
1502
- }
1503
- }
1504
- // ------------------------------------------------------------------
1505
- // 4b. Which keys that properties file has to declare
1506
- // ------------------------------------------------------------------
1507
- //
1508
- // Section 4 says the file is absent; this says what is in it. The two are
1509
- // separate because they answer different questions for different readers —
1510
- // the warnings above tell a person a file is missing, and this tells a
1511
- // generator what a stand-in for it has to contain.
1512
- // ------------------------------------------------------------------
1513
- const missingPropertyKeys = collectPropertyKeys(gradleFiles);
1514
- // ------------------------------------------------------------------
1515
- // 5. Firebase / GMS — check google-services.json after scanning all files
1516
- // ------------------------------------------------------------------
1517
- if (gmsDetected) {
1518
- // google-services.json can live in the root or in the app module dir
1519
- const candidatePaths = [
1520
- resolve(projectRoot, "google-services.json"),
1521
- resolve(projectRoot, "app", "google-services.json"),
1522
- ];
1523
- const found = candidatePaths.some((p) => existsSync(p));
1524
- if (!found) {
1525
- warnings.push({
1526
- category: "firebase",
1527
- severity: "warning",
1528
- message: `google-services.json not found (GMS plugin detected in ${gmsDetectedIn})`,
1529
- hint: "Add google-services.json as a secret file or generate it from env vars at build time",
1530
- });
1531
- }
1532
- }
1533
- // ------------------------------------------------------------------
1534
- // 5. Signing config summary warning (if detected and not already specific)
1535
- // ------------------------------------------------------------------
1536
- if (signingDetected) {
1537
- warnings.push({
1538
- category: "signing-config",
1539
- severity: "info",
1540
- message: `signingConfigs block found in ${signingDetectedIn}`,
1541
- hint: "Ensure keystore file and all signing credentials are available in the CI environment",
1542
- });
1543
- }
1544
- // ------------------------------------------------------------------
1545
- // 6. Gitignore cross-check
1546
- // ------------------------------------------------------------------
1547
- for (const item of gitignoreItems) {
1548
- // Only warn if we haven't already emitted a more specific warning for it
1549
- const alreadyCovered = (item === "google-services.json" && gmsDetected) ||
1550
- (item === "local.properties" && !existsSync(resolve(projectRoot, "local.properties")));
1551
- if (!alreadyCovered) {
1552
- warnings.push({
1553
- category: "missing-file",
1554
- severity: "info",
1555
- message: `${item} is in .gitignore — likely absent in CI`,
1556
- hint: "Provide this file as a secret or generate it at build time",
1557
- });
1558
- }
1559
- }
1560
- return {
1561
- warnings,
1562
- detectedJavaVersion,
1563
- detectedJavaVersionSource,
1564
- detectedGradleVersion,
1565
- buildVariants,
1566
- missingPropertyKeys,
1567
- };
1568
- }
1569
- // ---------------------------------------------------------------------------
1570
- // Formatter
1571
- // ---------------------------------------------------------------------------
1572
- const CATEGORY_LABELS = {
1573
- "missing-file": "Missing files",
1574
- "env-var": "Environment variables referenced in Gradle files",
1575
- "gradle-property": "Gradle properties referenced",
1576
- "signing-config": "Signing configuration",
1577
- "build-config": "BuildConfig env vars",
1578
- "firebase": "Firebase / GMS",
1579
- "secrets-plugin": "Google Secrets Gradle Plugin (local.properties keys)",
1580
- };
1581
- export function formatScanResult(result) {
1582
- const lines = [];
1583
- const sep = "─".repeat(50);
1584
- // Always show the detected Java version, and the one the pipeline will
1585
- // actually name — they differ whenever the declaration is below the oldest
1586
- // installed JDK or above the newest, and saying only the first made the
1587
- // second look like it had been honoured.
1588
- //
1589
- // And when they differ, say why in the same breath. An answer that is not
1590
- // what was asked for, printed with no reason, reads as a fact about the
1591
- // project rather than as a decision made on its behalf.
1592
- if (result.detectedJavaVersion !== undefined) {
1593
- const willUse = runnableJavaVersion(result.detectedJavaVersion, result.detectedGradleVersion);
1594
- lines.push(willUse === undefined
1595
- ? `ℹ Detected Java ${result.detectedJavaVersion} requirement — no installed JDK can satisfy it`
1596
- : `ℹ Detected Java ${result.detectedJavaVersion} requirement — pipeline will use Java ${willUse}`);
1597
- const problem = javaVersionProblem(result.detectedJavaVersion, result.detectedGradleVersion, result.detectedJavaVersionSource);
1598
- if (problem)
1599
- lines.push(` ${problem}`);
1600
- lines.push("");
1601
- }
1602
- if (result.warnings.length === 0) {
1603
- lines.push("✅ No potential unknowns detected.");
1604
- return lines.join("\n") + "\n";
1605
- }
1606
- lines.push(`⚠ Android Project Scan — Potential Unknowns`);
1607
- lines.push(sep);
1608
- lines.push("");
1609
- // Group by category
1610
- const byCategory = new Map();
1611
- for (const w of result.warnings) {
1612
- if (!byCategory.has(w.category))
1613
- byCategory.set(w.category, []);
1614
- byCategory.get(w.category).push(w);
1615
- }
1616
- for (const [cat, items] of byCategory) {
1617
- lines.push(`${CATEGORY_LABELS[cat]}:`);
1618
- for (const w of items) {
1619
- const icon = w.severity === "warning" ? "⚠ " : "ℹ ";
1620
- const loc = w.location ? ` (${w.location})` : "";
1621
- lines.push(` ${icon} ${w.message}${loc}`);
1622
- if (w.hint) {
1623
- lines.push(` Hint: ${w.hint}`);
1624
- }
1625
- }
1626
- lines.push("");
1627
- }
1628
- lines.push(sep);
1629
- const warnCount = result.warnings.filter((w) => w.severity === "warning").length;
1630
- const infoCount = result.warnings.filter((w) => w.severity === "info").length;
1631
- const parts = [];
1632
- if (warnCount > 0)
1633
- parts.push(`${warnCount} warning(s)`);
1634
- if (infoCount > 0)
1635
- parts.push(`${infoCount} info`);
1636
- lines.push(`${parts.join(", ")} found. Review before running on CI.`);
1637
- return lines.join("\n");
1638
- }
1639
- //# sourceMappingURL=android-scanner.js.map