@invarn/cibuild 1.3.16 → 1.3.17

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 (242) hide show
  1. package/dist/cli.cjs +1 -1
  2. package/dist/src/cli.d.ts +3 -0
  3. package/dist/src/cli.d.ts.map +1 -0
  4. package/dist/src/cli.js +987 -0
  5. package/dist/src/commands/android-scanner.d.ts +32 -0
  6. package/dist/src/commands/android-scanner.d.ts.map +1 -0
  7. package/dist/src/commands/android-scanner.js +667 -0
  8. package/dist/src/commands/build.d.ts +5 -0
  9. package/dist/src/commands/build.d.ts.map +1 -0
  10. package/dist/src/commands/build.js +1096 -0
  11. package/dist/src/commands/edit.d.ts +3 -0
  12. package/dist/src/commands/edit.d.ts.map +1 -0
  13. package/dist/src/commands/edit.js +651 -0
  14. package/dist/src/commands/file-secret-collector.d.ts +37 -0
  15. package/dist/src/commands/file-secret-collector.d.ts.map +1 -0
  16. package/dist/src/commands/file-secret-collector.js +199 -0
  17. package/dist/src/commands/github-workflow.d.ts +5 -0
  18. package/dist/src/commands/github-workflow.d.ts.map +1 -0
  19. package/dist/src/commands/github-workflow.js +45 -0
  20. package/dist/src/commands/ios-scanner.d.ts +27 -0
  21. package/dist/src/commands/ios-scanner.d.ts.map +1 -0
  22. package/dist/src/commands/ios-scanner.js +337 -0
  23. package/dist/src/commands/reset.d.ts +7 -0
  24. package/dist/src/commands/reset.d.ts.map +1 -0
  25. package/dist/src/commands/reset.js +81 -0
  26. package/dist/src/commands/secrets-sync-workflow.d.ts +15 -0
  27. package/dist/src/commands/secrets-sync-workflow.d.ts.map +1 -0
  28. package/dist/src/commands/secrets-sync-workflow.js +255 -0
  29. package/dist/src/commands/secrets-upload.d.ts +21 -0
  30. package/dist/src/commands/secrets-upload.d.ts.map +1 -0
  31. package/dist/src/commands/secrets-upload.js +177 -0
  32. package/dist/src/commands/secrets-upload.test.d.ts +5 -0
  33. package/dist/src/commands/secrets-upload.test.d.ts.map +1 -0
  34. package/dist/src/commands/secrets-upload.test.js +60 -0
  35. package/dist/src/config.d.ts +3 -0
  36. package/dist/src/config.d.ts.map +1 -0
  37. package/dist/src/config.js +46 -0
  38. package/dist/src/envman/cli.d.ts +21 -0
  39. package/dist/src/envman/cli.d.ts.map +1 -0
  40. package/dist/src/envman/cli.js +240 -0
  41. package/dist/src/envman/envman.d.ts +83 -0
  42. package/dist/src/envman/envman.d.ts.map +1 -0
  43. package/dist/src/envman/envman.js +361 -0
  44. package/dist/src/envman/envman.test.d.ts +5 -0
  45. package/dist/src/envman/envman.test.d.ts.map +1 -0
  46. package/dist/src/envman/envman.test.js +236 -0
  47. package/dist/src/envman/index.d.ts +23 -0
  48. package/dist/src/envman/index.d.ts.map +1 -0
  49. package/dist/src/envman/index.js +23 -0
  50. package/dist/src/envman/types.d.ts +55 -0
  51. package/dist/src/envman/types.d.ts.map +1 -0
  52. package/dist/src/envman/types.js +12 -0
  53. package/dist/src/lib.d.ts +27 -0
  54. package/dist/src/lib.d.ts.map +1 -0
  55. package/dist/src/lib.js +32 -0
  56. package/dist/src/pipeline.d.ts +3 -0
  57. package/dist/src/pipeline.d.ts.map +1 -0
  58. package/dist/src/pipeline.js +57 -0
  59. package/dist/src/runner.d.ts +17 -0
  60. package/dist/src/runner.d.ts.map +1 -0
  61. package/dist/src/runner.js +234 -0
  62. package/dist/src/types.d.ts +57 -0
  63. package/dist/src/types.d.ts.map +1 -0
  64. package/dist/src/types.js +2 -0
  65. package/dist/src/yaml/bitrise-compat.d.ts +65 -0
  66. package/dist/src/yaml/bitrise-compat.d.ts.map +1 -0
  67. package/dist/src/yaml/bitrise-compat.js +206 -0
  68. package/dist/src/yaml/bitrise-compat.test.d.ts +5 -0
  69. package/dist/src/yaml/bitrise-compat.test.d.ts.map +1 -0
  70. package/dist/src/yaml/bitrise-compat.test.js +347 -0
  71. package/dist/src/yaml/converter.d.ts +33 -0
  72. package/dist/src/yaml/converter.d.ts.map +1 -0
  73. package/dist/src/yaml/converter.js +222 -0
  74. package/dist/src/yaml/converter.test.d.ts +5 -0
  75. package/dist/src/yaml/converter.test.d.ts.map +1 -0
  76. package/dist/src/yaml/converter.test.js +348 -0
  77. package/dist/src/yaml/e2e.test.d.ts +6 -0
  78. package/dist/src/yaml/e2e.test.d.ts.map +1 -0
  79. package/dist/src/yaml/e2e.test.js +446 -0
  80. package/dist/src/yaml/env-resolver.d.ts +120 -0
  81. package/dist/src/yaml/env-resolver.d.ts.map +1 -0
  82. package/dist/src/yaml/env-resolver.js +405 -0
  83. package/dist/src/yaml/env-resolver.test.d.ts +5 -0
  84. package/dist/src/yaml/env-resolver.test.d.ts.map +1 -0
  85. package/dist/src/yaml/env-resolver.test.js +502 -0
  86. package/dist/src/yaml/interactive-prompts.d.ts +71 -0
  87. package/dist/src/yaml/interactive-prompts.d.ts.map +1 -0
  88. package/dist/src/yaml/interactive-prompts.js +258 -0
  89. package/dist/src/yaml/missing-env-handler.d.ts +45 -0
  90. package/dist/src/yaml/missing-env-handler.d.ts.map +1 -0
  91. package/dist/src/yaml/missing-env-handler.js +64 -0
  92. package/dist/src/yaml/parser.d.ts +33 -0
  93. package/dist/src/yaml/parser.d.ts.map +1 -0
  94. package/dist/src/yaml/parser.js +145 -0
  95. package/dist/src/yaml/pipeline-with-secrets.d.ts +25 -0
  96. package/dist/src/yaml/pipeline-with-secrets.d.ts.map +1 -0
  97. package/dist/src/yaml/pipeline-with-secrets.js +76 -0
  98. package/dist/src/yaml/platform-detector.d.ts +83 -0
  99. package/dist/src/yaml/platform-detector.d.ts.map +1 -0
  100. package/dist/src/yaml/platform-detector.js +188 -0
  101. package/dist/src/yaml/platform-detector.test.d.ts +5 -0
  102. package/dist/src/yaml/platform-detector.test.d.ts.map +1 -0
  103. package/dist/src/yaml/platform-detector.test.js +414 -0
  104. package/dist/src/yaml/preflight-validation.d.ts +40 -0
  105. package/dist/src/yaml/preflight-validation.d.ts.map +1 -0
  106. package/dist/src/yaml/preflight-validation.js +152 -0
  107. package/dist/src/yaml/secrets-manager.d.ts +77 -0
  108. package/dist/src/yaml/secrets-manager.d.ts.map +1 -0
  109. package/dist/src/yaml/secrets-manager.js +219 -0
  110. package/dist/src/yaml/step-validator.d.ts +54 -0
  111. package/dist/src/yaml/step-validator.d.ts.map +1 -0
  112. package/dist/src/yaml/step-validator.js +403 -0
  113. package/dist/src/yaml/steps/android-sign.d.ts +35 -0
  114. package/dist/src/yaml/steps/android-sign.d.ts.map +1 -0
  115. package/dist/src/yaml/steps/android-sign.js +147 -0
  116. package/dist/src/yaml/steps/android-version.d.ts +26 -0
  117. package/dist/src/yaml/steps/android-version.d.ts.map +1 -0
  118. package/dist/src/yaml/steps/android-version.js +128 -0
  119. package/dist/src/yaml/steps/android-version.test.d.ts +5 -0
  120. package/dist/src/yaml/steps/android-version.test.d.ts.map +1 -0
  121. package/dist/src/yaml/steps/android-version.test.js +196 -0
  122. package/dist/src/yaml/steps/android.d.ts +95 -0
  123. package/dist/src/yaml/steps/android.d.ts.map +1 -0
  124. package/dist/src/yaml/steps/android.js +916 -0
  125. package/dist/src/yaml/steps/app-store-deploy.d.ts +48 -0
  126. package/dist/src/yaml/steps/app-store-deploy.d.ts.map +1 -0
  127. package/dist/src/yaml/steps/app-store-deploy.js +162 -0
  128. package/dist/src/yaml/steps/base.d.ts +238 -0
  129. package/dist/src/yaml/steps/base.d.ts.map +1 -0
  130. package/dist/src/yaml/steps/base.js +345 -0
  131. package/dist/src/yaml/steps/bitrise-android-tools.d.ts +26 -0
  132. package/dist/src/yaml/steps/bitrise-android-tools.d.ts.map +1 -0
  133. package/dist/src/yaml/steps/bitrise-android-tools.js +198 -0
  134. package/dist/src/yaml/steps/bitrise-android-tools.test.d.ts +5 -0
  135. package/dist/src/yaml/steps/bitrise-android-tools.test.d.ts.map +1 -0
  136. package/dist/src/yaml/steps/bitrise-android-tools.test.js +280 -0
  137. package/dist/src/yaml/steps/bitrise-apk-info.d.ts +22 -0
  138. package/dist/src/yaml/steps/bitrise-apk-info.d.ts.map +1 -0
  139. package/dist/src/yaml/steps/bitrise-apk-info.js +144 -0
  140. package/dist/src/yaml/steps/bitrise-apk-info.test.d.ts +5 -0
  141. package/dist/src/yaml/steps/bitrise-apk-info.test.d.ts.map +1 -0
  142. package/dist/src/yaml/steps/bitrise-apk-info.test.js +331 -0
  143. package/dist/src/yaml/steps/bitrise-slack.d.ts +49 -0
  144. package/dist/src/yaml/steps/bitrise-slack.d.ts.map +1 -0
  145. package/dist/src/yaml/steps/bitrise-slack.js +280 -0
  146. package/dist/src/yaml/steps/bitrise-slack.test.d.ts +5 -0
  147. package/dist/src/yaml/steps/bitrise-slack.test.d.ts.map +1 -0
  148. package/dist/src/yaml/steps/bitrise-slack.test.js +484 -0
  149. package/dist/src/yaml/steps/bitrise-ssh.d.ts +27 -0
  150. package/dist/src/yaml/steps/bitrise-ssh.d.ts.map +1 -0
  151. package/dist/src/yaml/steps/bitrise-ssh.js +134 -0
  152. package/dist/src/yaml/steps/bitrise-ssh.test.d.ts +5 -0
  153. package/dist/src/yaml/steps/bitrise-ssh.test.d.ts.map +1 -0
  154. package/dist/src/yaml/steps/bitrise-ssh.test.js +205 -0
  155. package/dist/src/yaml/steps/cache.d.ts +52 -0
  156. package/dist/src/yaml/steps/cache.d.ts.map +1 -0
  157. package/dist/src/yaml/steps/cache.js +351 -0
  158. package/dist/src/yaml/steps/fastlane.d.ts +27 -0
  159. package/dist/src/yaml/steps/fastlane.d.ts.map +1 -0
  160. package/dist/src/yaml/steps/fastlane.js +79 -0
  161. package/dist/src/yaml/steps/file.d.ts +27 -0
  162. package/dist/src/yaml/steps/file.d.ts.map +1 -0
  163. package/dist/src/yaml/steps/file.js +35 -0
  164. package/dist/src/yaml/steps/flutter.d.ts +63 -0
  165. package/dist/src/yaml/steps/flutter.d.ts.map +1 -0
  166. package/dist/src/yaml/steps/flutter.js +215 -0
  167. package/dist/src/yaml/steps/git-clone.d.ts +26 -0
  168. package/dist/src/yaml/steps/git-clone.d.ts.map +1 -0
  169. package/dist/src/yaml/steps/git-clone.js +111 -0
  170. package/dist/src/yaml/steps/google-play-deploy.d.ts +37 -0
  171. package/dist/src/yaml/steps/google-play-deploy.d.ts.map +1 -0
  172. package/dist/src/yaml/steps/google-play-deploy.js +193 -0
  173. package/dist/src/yaml/steps/google-play-deploy.test.d.ts +5 -0
  174. package/dist/src/yaml/steps/google-play-deploy.test.d.ts.map +1 -0
  175. package/dist/src/yaml/steps/google-play-deploy.test.js +310 -0
  176. package/dist/src/yaml/steps/index.d.ts +10 -0
  177. package/dist/src/yaml/steps/index.d.ts.map +1 -0
  178. package/dist/src/yaml/steps/index.js +1361 -0
  179. package/dist/src/yaml/steps/ios-deps.d.ts +43 -0
  180. package/dist/src/yaml/steps/ios-deps.d.ts.map +1 -0
  181. package/dist/src/yaml/steps/ios-deps.js +141 -0
  182. package/dist/src/yaml/steps/ios-deps.test.d.ts +5 -0
  183. package/dist/src/yaml/steps/ios-deps.test.d.ts.map +1 -0
  184. package/dist/src/yaml/steps/ios-deps.test.js +90 -0
  185. package/dist/src/yaml/steps/ios-signing.d.ts +31 -0
  186. package/dist/src/yaml/steps/ios-signing.d.ts.map +1 -0
  187. package/dist/src/yaml/steps/ios-signing.js +144 -0
  188. package/dist/src/yaml/steps/ios-version.d.ts +47 -0
  189. package/dist/src/yaml/steps/ios-version.d.ts.map +1 -0
  190. package/dist/src/yaml/steps/ios-version.js +151 -0
  191. package/dist/src/yaml/steps/linting.d.ts +47 -0
  192. package/dist/src/yaml/steps/linting.d.ts.map +1 -0
  193. package/dist/src/yaml/steps/linting.js +148 -0
  194. package/dist/src/yaml/steps/phase2.test.d.ts +6 -0
  195. package/dist/src/yaml/steps/phase2.test.d.ts.map +1 -0
  196. package/dist/src/yaml/steps/phase2.test.js +197 -0
  197. package/dist/src/yaml/steps/phase3.test.d.ts +5 -0
  198. package/dist/src/yaml/steps/phase3.test.d.ts.map +1 -0
  199. package/dist/src/yaml/steps/phase3.test.js +144 -0
  200. package/dist/src/yaml/steps/phase4.test.d.ts +5 -0
  201. package/dist/src/yaml/steps/phase4.test.d.ts.map +1 -0
  202. package/dist/src/yaml/steps/phase4.test.js +166 -0
  203. package/dist/src/yaml/steps/phase5.test.d.ts +6 -0
  204. package/dist/src/yaml/steps/phase5.test.d.ts.map +1 -0
  205. package/dist/src/yaml/steps/phase5.test.js +263 -0
  206. package/dist/src/yaml/steps/registry.d.ts +88 -0
  207. package/dist/src/yaml/steps/registry.d.ts.map +1 -0
  208. package/dist/src/yaml/steps/registry.js +125 -0
  209. package/dist/src/yaml/steps/registry.test.d.ts +5 -0
  210. package/dist/src/yaml/steps/registry.test.d.ts.map +1 -0
  211. package/dist/src/yaml/steps/registry.test.js +235 -0
  212. package/dist/src/yaml/steps/release.d.ts +50 -0
  213. package/dist/src/yaml/steps/release.d.ts.map +1 -0
  214. package/dist/src/yaml/steps/release.js +154 -0
  215. package/dist/src/yaml/steps/script.d.ts +23 -0
  216. package/dist/src/yaml/steps/script.d.ts.map +1 -0
  217. package/dist/src/yaml/steps/script.js +63 -0
  218. package/dist/src/yaml/steps/spec-validation.test.d.ts +6 -0
  219. package/dist/src/yaml/steps/spec-validation.test.d.ts.map +1 -0
  220. package/dist/src/yaml/steps/spec-validation.test.js +130 -0
  221. package/dist/src/yaml/steps/steps.test.d.ts +6 -0
  222. package/dist/src/yaml/steps/steps.test.d.ts.map +1 -0
  223. package/dist/src/yaml/steps/steps.test.js +474 -0
  224. package/dist/src/yaml/steps/test-config.d.ts +3 -0
  225. package/dist/src/yaml/steps/test-config.d.ts.map +1 -0
  226. package/dist/src/yaml/steps/test-config.js +16 -0
  227. package/dist/src/yaml/steps/xcode-new.test.d.ts +5 -0
  228. package/dist/src/yaml/steps/xcode-new.test.d.ts.map +1 -0
  229. package/dist/src/yaml/steps/xcode-new.test.js +211 -0
  230. package/dist/src/yaml/steps/xcode.d.ts +222 -0
  231. package/dist/src/yaml/steps/xcode.d.ts.map +1 -0
  232. package/dist/src/yaml/steps/xcode.js +999 -0
  233. package/dist/src/yaml/types.d.ts +68 -0
  234. package/dist/src/yaml/types.d.ts.map +1 -0
  235. package/dist/src/yaml/types.js +5 -0
  236. package/dist/src/yaml/validation-types.d.ts +96 -0
  237. package/dist/src/yaml/validation-types.d.ts.map +1 -0
  238. package/dist/src/yaml/validation-types.js +8 -0
  239. package/dist/src/yaml/yaml-updater.d.ts +24 -0
  240. package/dist/src/yaml/yaml-updater.d.ts.map +1 -0
  241. package/dist/src/yaml/yaml-updater.js +128 -0
  242. package/package.json +16 -4
@@ -0,0 +1,667 @@
1
+ import { resolve, relative } from "node:path";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ // ---------------------------------------------------------------------------
4
+ // File discovery
5
+ // ---------------------------------------------------------------------------
6
+ /**
7
+ * Returns the list of gradle files to scan.
8
+ * Always includes the root build.gradle and the app/ module.
9
+ * Also parses settings.gradle to find any additional include'd modules.
10
+ */
11
+ function findGradleFiles(root) {
12
+ const candidates = [];
13
+ // Root-level build files
14
+ for (const name of ["build.gradle", "build.gradle.kts"]) {
15
+ const p = resolve(root, name);
16
+ if (existsSync(p))
17
+ candidates.push(p);
18
+ }
19
+ // Collect module directories from settings.gradle
20
+ const moduleDirs = new Set(["app"]); // always include app/
21
+ for (const name of ["settings.gradle", "settings.gradle.kts"]) {
22
+ const p = resolve(root, name);
23
+ if (!existsSync(p))
24
+ continue;
25
+ const content = readFileSync(p, "utf-8");
26
+ // Matches: include ':module', include(":module"), include ':a', ':b'
27
+ const includeRe = /include\s*[\(']?\s*['"]:([^'")\s,]+)/g;
28
+ let m;
29
+ while ((m = includeRe.exec(content)) !== null) {
30
+ moduleDirs.add(m[1].replace(/:/g, "/"));
31
+ }
32
+ }
33
+ for (const mod of moduleDirs) {
34
+ for (const name of ["build.gradle", "build.gradle.kts"]) {
35
+ const p = resolve(root, mod, name);
36
+ if (existsSync(p))
37
+ candidates.push(p);
38
+ }
39
+ }
40
+ return candidates;
41
+ }
42
+ function safeRead(path) {
43
+ try {
44
+ return readFileSync(path, "utf-8");
45
+ }
46
+ catch {
47
+ return "";
48
+ }
49
+ }
50
+ function relPath(root, filePath) {
51
+ return relative(root, filePath);
52
+ }
53
+ /**
54
+ * Returns line numbers for each match of a regex in file content.
55
+ */
56
+ function lineNumber(content, index) {
57
+ return content.slice(0, index).split("\n").length;
58
+ }
59
+ /** Extract all System.getenv("VAR") references. */
60
+ function extractEnvVarRefs(content) {
61
+ const results = [];
62
+ const re = /System\.getenv\(\s*["']([^"']+)["']\s*\)/g;
63
+ let m;
64
+ while ((m = re.exec(content)) !== null) {
65
+ results.push({ name: m[1], line: lineNumber(content, m.index) });
66
+ }
67
+ return results;
68
+ }
69
+ /** Extract Groovy findProperty("prop") and Kotlin properties["prop"] references. */
70
+ function extractPropertyRefs(content) {
71
+ const results = [];
72
+ // Groovy: project.findProperty("name") or findProperty("name")
73
+ const groovyRe = /(?:project\.)?findProperty\(\s*["']([^"']+)["']\s*\)/g;
74
+ let m;
75
+ while ((m = groovyRe.exec(content)) !== null) {
76
+ results.push({ name: m[1], line: lineNumber(content, m.index) });
77
+ }
78
+ // Kotlin DSL: properties["name"] or project.properties["name"]
79
+ const kotlinRe = /(?:project\.)?properties\["([^"]+)"\]/g;
80
+ while ((m = kotlinRe.exec(content)) !== null) {
81
+ results.push({ name: m[1], line: lineNumber(content, m.index) });
82
+ }
83
+ return results;
84
+ }
85
+ /**
86
+ * Extract env var names from buildConfigField entries.
87
+ * e.g. buildConfigField "String", "API_KEY", System.getenv("MAPS_API_KEY") ?: ""
88
+ * Returns the System.getenv variable names embedded inside buildConfigField values.
89
+ */
90
+ function extractBuildConfigEnvRefs(content) {
91
+ const results = [];
92
+ // Match buildConfigField lines that also contain System.getenv
93
+ const lineRe = /buildConfigField\s*[\("]/g;
94
+ let m;
95
+ const envRe = /System\.getenv\(\s*["']([^"']+)["']\s*\)/g;
96
+ while ((m = lineRe.exec(content)) !== null) {
97
+ // Find the end of the statement (next newline or semicolon)
98
+ const lineEnd = content.indexOf("\n", m.index);
99
+ const slice = content.slice(m.index, lineEnd === -1 ? undefined : lineEnd + 1);
100
+ let em;
101
+ while ((em = envRe.exec(slice)) !== null) {
102
+ results.push({ name: em[1], line: lineNumber(content, m.index) });
103
+ }
104
+ envRe.lastIndex = 0;
105
+ }
106
+ return results;
107
+ }
108
+ /**
109
+ * Returns true if the Google Secrets Gradle Plugin is applied.
110
+ * This plugin reads local.properties (or a configured file) and auto-generates
111
+ * BuildConfig fields — so if that file is absent in CI, fields won't be generated.
112
+ */
113
+ function detectSecretsPlugin(content) {
114
+ return (/alias\s*\(\s*libs\.plugins\.secrets\s*\)/.test(content) ||
115
+ /id\s*\(\s*["']com\.google\.android\.libraries\.mapsplatform\.secrets-gradle-plugin["']\s*\)/.test(content) ||
116
+ /id\s*["']com\.google\.android\.libraries\.mapsplatform\.secrets-gradle-plugin["']/.test(content));
117
+ }
118
+ /**
119
+ * Extracts the `propertiesFileName` from a `secrets { }` block.
120
+ * Defaults to "local.properties" if not configured.
121
+ */
122
+ function extractSecretsPropertiesFile(content) {
123
+ const block = extractBlockContent(content, "secrets");
124
+ if (!block)
125
+ return "local.properties";
126
+ const m = /propertiesFileName\s*=\s*["']([^"']+)["']/.exec(block);
127
+ return m ? m[1] : "local.properties";
128
+ }
129
+ /** Parse property keys from a standard .properties file, skipping comments and blank lines. */
130
+ function parsePropertyKeys(propertiesContent) {
131
+ return propertiesContent
132
+ .split("\n")
133
+ .map((l) => l.trim())
134
+ .filter((l) => l && !l.startsWith("#") && l.includes("="))
135
+ .map((l) => l.split("=")[0].trim())
136
+ .filter(Boolean);
137
+ }
138
+ /** Returns true if the GMS google-services plugin is applied in this content. */
139
+ function detectGmsPlugin(content) {
140
+ return (/apply\s+plugin\s*:\s*['"]com\.google\.gms\.google-services['"]/.test(content) ||
141
+ /id\s*\(\s*["']com\.google\.gms\.google-services["']\s*\)/.test(content) ||
142
+ /id\s*["']com\.google\.gms\.google-services["']/.test(content));
143
+ }
144
+ /** Returns true if a signingConfigs block exists. */
145
+ function detectSigningConfigs(content) {
146
+ return /signingConfigs\s*\{/.test(content);
147
+ }
148
+ /**
149
+ * Extract env var and property references from within signingConfigs blocks.
150
+ * Looks for storeFile, storePassword, keyAlias, keyPassword assignments.
151
+ */
152
+ function parseSigningCredentialRefs(content) {
153
+ const results = [];
154
+ const blockRe = /signingConfigs\s*\{([\s\S]*?)^\s*\}/m;
155
+ const match = blockRe.exec(content);
156
+ if (!match)
157
+ return results;
158
+ const block = match[1];
159
+ const blockStart = match.index;
160
+ // System.getenv inside signing block
161
+ const envRe = /System\.getenv\(\s*["']([^"']+)["']\s*\)/g;
162
+ let m;
163
+ while ((m = envRe.exec(block)) !== null) {
164
+ results.push({ name: m[1], line: lineNumber(content, blockStart + m.index) });
165
+ }
166
+ // findProperty / properties[] inside signing block
167
+ const propRe = /(?:project\.)?findProperty\(\s*["']([^"']+)["']\s*\)|(?:project\.)?properties\["([^"]+)"\]/g;
168
+ while ((m = propRe.exec(block)) !== null) {
169
+ results.push({ name: m[1] ?? m[2], line: lineNumber(content, blockStart + m.index) });
170
+ }
171
+ return results;
172
+ }
173
+ /**
174
+ * Extract referenced keystore file paths from signingConfigs.
175
+ * Handles: storeFile file("path") and storeFile = file("path")
176
+ */
177
+ function extractKeystorePaths(content) {
178
+ const paths = [];
179
+ const re = /storeFile\s*(?:=\s*)?file\(\s*["']([^"']+)["']\s*\)/g;
180
+ let m;
181
+ while ((m = re.exec(content)) !== null) {
182
+ paths.push(m[1]);
183
+ }
184
+ return paths;
185
+ }
186
+ // ---------------------------------------------------------------------------
187
+ // Java version detection
188
+ // ---------------------------------------------------------------------------
189
+ /**
190
+ * Parses a raw version token into a major Java version number.
191
+ * Handles formats: "21", "17", "1.8" (→ 8), "VERSION_21", "VERSION_1_8" (→ 8).
192
+ */
193
+ function parseJavaVersion(raw) {
194
+ // JavaVersion enum: VERSION_21, VERSION_17, VERSION_1_8
195
+ const enumMatch = /VERSION_1_(\d)$/.exec(raw);
196
+ if (enumMatch)
197
+ return parseInt(enumMatch[1], 10);
198
+ const enumMatch2 = /VERSION_(\d+)$/.exec(raw);
199
+ if (enumMatch2)
200
+ return parseInt(enumMatch2[1], 10);
201
+ // String literal: "21", "17", "11", "1.8"
202
+ const legacyMatch = /^1\.(\d)$/.exec(raw.trim());
203
+ if (legacyMatch)
204
+ return parseInt(legacyMatch[1], 10);
205
+ const plain = parseInt(raw.trim(), 10);
206
+ if (!isNaN(plain))
207
+ return plain;
208
+ return undefined;
209
+ }
210
+ /**
211
+ * Scans Gradle file content for Java / JVM version requirements and returns
212
+ * the highest version number found.
213
+ *
214
+ * Detects:
215
+ * - sourceCompatibility / targetCompatibility (Groovy & Kotlin DSL)
216
+ * - kotlinOptions { jvmTarget = "21" }
217
+ * - jvmToolchain(21) / jvmToolchain { languageVersion.set(JavaLanguageVersion.of(21)) }
218
+ * - java { toolchain { languageVersion.set(JavaLanguageVersion.of(21)) } }
219
+ */
220
+ function detectJavaVersionInContent(content) {
221
+ const candidates = [];
222
+ // sourceCompatibility / targetCompatibility = JavaVersion.VERSION_21 or = "21" or = JavaVersion.VERSION_1_8
223
+ const compatRe = /(?:sourceCompatibility|targetCompatibility)\s*[=\s]+([A-Za-z0-9._]+)/g;
224
+ let m;
225
+ while ((m = compatRe.exec(content)) !== null) {
226
+ const v = parseJavaVersion(m[1]);
227
+ if (v)
228
+ candidates.push(v);
229
+ }
230
+ // jvmTarget = "21" (inside kotlinOptions or compileOptions)
231
+ const jvmTargetRe = /jvmTarget\s*=\s*["']([^"']+)["']/g;
232
+ while ((m = jvmTargetRe.exec(content)) !== null) {
233
+ const v = parseJavaVersion(m[1]);
234
+ if (v)
235
+ candidates.push(v);
236
+ }
237
+ // jvmToolchain(21)
238
+ const toolchainRe = /jvmToolchain\s*\(\s*(\d+)\s*\)/g;
239
+ while ((m = toolchainRe.exec(content)) !== null) {
240
+ candidates.push(parseInt(m[1], 10));
241
+ }
242
+ // JavaLanguageVersion.of(21)
243
+ const langVerRe = /JavaLanguageVersion\.of\s*\(\s*(\d+)\s*\)/g;
244
+ while ((m = langVerRe.exec(content)) !== null) {
245
+ candidates.push(parseInt(m[1], 10));
246
+ }
247
+ return candidates.length > 0 ? Math.max(...candidates) : undefined;
248
+ }
249
+ /**
250
+ * Returns the highest Java version requirement found across all Gradle files,
251
+ * or undefined if none is detected.
252
+ */
253
+ function detectRequiredJavaVersion(gradleFiles) {
254
+ const versions = [];
255
+ for (const filePath of gradleFiles) {
256
+ const content = safeRead(filePath);
257
+ const v = detectJavaVersionInContent(content);
258
+ if (v !== undefined)
259
+ versions.push(v);
260
+ }
261
+ return versions.length > 0 ? Math.max(...versions) : undefined;
262
+ }
263
+ // ---------------------------------------------------------------------------
264
+ // Gitignore helper
265
+ // ---------------------------------------------------------------------------
266
+ const CI_RELEVANT_GITIGNORE_PATTERNS = [
267
+ { pattern: /google-services\.json/, label: "google-services.json" },
268
+ { pattern: /local\.properties/, label: "local.properties" },
269
+ { pattern: /keystore\.properties/, label: "keystore.properties" },
270
+ { pattern: /\.jks$|\.keystore$|\*\.jks|\*\.keystore/, label: "keystore files (*.jks / *.keystore)" },
271
+ { pattern: /\.env$|\*\.env/, label: ".env files" },
272
+ ];
273
+ function checkGitignore(root) {
274
+ const gitignorePath = resolve(root, ".gitignore");
275
+ if (!existsSync(gitignorePath))
276
+ return [];
277
+ const content = safeRead(gitignorePath);
278
+ const lines = content.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
279
+ const found = [];
280
+ for (const { pattern, label } of CI_RELEVANT_GITIGNORE_PATTERNS) {
281
+ if (lines.some((l) => pattern.test(l))) {
282
+ found.push(label);
283
+ }
284
+ }
285
+ return found;
286
+ }
287
+ // ---------------------------------------------------------------------------
288
+ // Build variant detection
289
+ // ---------------------------------------------------------------------------
290
+ /**
291
+ * Extracts the inner content of a named block using brace-counting.
292
+ * e.g. extractBlockContent(content, "buildTypes") returns everything inside buildTypes { ... }.
293
+ * Returns undefined if the block is not found.
294
+ */
295
+ function extractBlockContent(content, keyword) {
296
+ const keywordRe = new RegExp(`\\b${keyword}\\s*\\{`);
297
+ const match = keywordRe.exec(content);
298
+ if (!match)
299
+ return undefined;
300
+ const start = content.indexOf("{", match.index);
301
+ let depth = 0;
302
+ for (let i = start; i < content.length; i++) {
303
+ if (content[i] === "{")
304
+ depth++;
305
+ else if (content[i] === "}") {
306
+ depth--;
307
+ if (depth === 0)
308
+ return content.slice(start + 1, i);
309
+ }
310
+ }
311
+ return undefined;
312
+ }
313
+ const CONTROL_KEYWORDS = new Set([
314
+ "if", "else", "for", "while", "when", "try", "catch", "finally",
315
+ "return", "val", "var", "fun", "object", "class", "interface",
316
+ ]);
317
+ /**
318
+ * Extracts the names of direct child blocks from block content.
319
+ * Handles both Groovy DSL (name { }) and Kotlin DSL (create("name") { } / getByName("name") { }).
320
+ */
321
+ function extractDirectChildNames(block) {
322
+ const names = [];
323
+ let depth = 0;
324
+ for (const line of block.split("\n")) {
325
+ const opens = (line.match(/\{/g) || []).length;
326
+ const closes = (line.match(/\}/g) || []).length;
327
+ if (depth === 0 && opens > 0) {
328
+ // Kotlin DSL: create("name") { or getByName("name") {
329
+ const kotlinMatch = /(?:create|getByName)\(\s*["'](\w+)["']\s*\)/.exec(line);
330
+ if (kotlinMatch) {
331
+ names.push(kotlinMatch[1]);
332
+ }
333
+ else {
334
+ // Groovy DSL: name { (identifier followed by optional whitespace then {)
335
+ const groovyMatch = /^\s*([a-z]\w*)\s*(?:\.\w+\s*)?\{/.exec(line);
336
+ if (groovyMatch && !CONTROL_KEYWORDS.has(groovyMatch[1])) {
337
+ names.push(groovyMatch[1]);
338
+ }
339
+ }
340
+ }
341
+ depth += opens - closes;
342
+ if (depth < 0)
343
+ depth = 0;
344
+ }
345
+ return [...new Set(names)];
346
+ }
347
+ /**
348
+ * Detects build types and product flavors from Gradle files by static parsing.
349
+ * Always includes "debug" and "release" as defaults.
350
+ */
351
+ export function detectBuildVariants(gradleFiles) {
352
+ const buildTypeSet = new Set(["debug", "release"]);
353
+ const productFlavorSet = new Set();
354
+ for (const filePath of gradleFiles) {
355
+ const content = safeRead(filePath);
356
+ const buildTypesBlock = extractBlockContent(content, "buildTypes");
357
+ if (buildTypesBlock) {
358
+ for (const name of extractDirectChildNames(buildTypesBlock)) {
359
+ buildTypeSet.add(name);
360
+ }
361
+ }
362
+ const productFlavorsBlock = extractBlockContent(content, "productFlavors");
363
+ if (productFlavorsBlock) {
364
+ for (const name of extractDirectChildNames(productFlavorsBlock)) {
365
+ productFlavorSet.add(name);
366
+ }
367
+ }
368
+ }
369
+ const buildTypes = Array.from(buildTypeSet);
370
+ const productFlavors = Array.from(productFlavorSet);
371
+ const variants = productFlavors.length === 0
372
+ ? buildTypes
373
+ : productFlavors.flatMap((flavor) => buildTypes.map((bt) => `${flavor}${bt.charAt(0).toUpperCase()}${bt.slice(1)}`));
374
+ return { buildTypes, productFlavors, variants };
375
+ }
376
+ // ---------------------------------------------------------------------------
377
+ // Main scanner
378
+ // ---------------------------------------------------------------------------
379
+ export async function scanAndroidProject(projectRoot) {
380
+ const warnings = [];
381
+ // Track env vars we've already warned about to avoid duplicates
382
+ const seenEnvVars = new Set();
383
+ const seenProperties = new Set();
384
+ // Compute gitignore items upfront — used in multiple sections below
385
+ const gitignoreItems = checkGitignore(projectRoot);
386
+ // ------------------------------------------------------------------
387
+ // 1. Missing files
388
+ // ------------------------------------------------------------------
389
+ if (!existsSync(resolve(projectRoot, "local.properties"))) {
390
+ warnings.push({
391
+ category: "missing-file",
392
+ severity: "warning",
393
+ message: "local.properties not found",
394
+ hint: "CI needs ANDROID_HOME set as an env var, or sdk.dir in local.properties",
395
+ });
396
+ }
397
+ // ------------------------------------------------------------------
398
+ // 2. Detect required Java version and build variants
399
+ // ------------------------------------------------------------------
400
+ const gradleFiles = findGradleFiles(projectRoot);
401
+ const detectedJavaVersion = detectRequiredJavaVersion(gradleFiles);
402
+ const buildVariants = detectBuildVariants(gradleFiles);
403
+ // ------------------------------------------------------------------
404
+ // 3. Scan Gradle files
405
+ // ------------------------------------------------------------------
406
+ let gmsDetected = false;
407
+ let gmsDetectedIn = "";
408
+ let signingDetected = false;
409
+ let signingDetectedIn = "";
410
+ for (const filePath of gradleFiles) {
411
+ const content = safeRead(filePath);
412
+ const rel = relPath(projectRoot, filePath);
413
+ // GMS plugin
414
+ if (detectGmsPlugin(content)) {
415
+ gmsDetected = true;
416
+ gmsDetectedIn = rel;
417
+ }
418
+ // Signing config
419
+ if (detectSigningConfigs(content)) {
420
+ signingDetected = true;
421
+ signingDetectedIn = rel;
422
+ // Keystore file paths referenced
423
+ for (const kPath of extractKeystorePaths(content)) {
424
+ const resolved = resolve(projectRoot, kPath);
425
+ if (!existsSync(resolved)) {
426
+ warnings.push({
427
+ category: "missing-file",
428
+ severity: "warning",
429
+ message: `Keystore file not found: ${kPath}`,
430
+ hint: "Provide the keystore file as a secret file in CI or update the path in signingConfigs",
431
+ location: rel,
432
+ });
433
+ }
434
+ }
435
+ // Signing credential env var / property refs
436
+ for (const ref of parseSigningCredentialRefs(content)) {
437
+ if (!seenEnvVars.has(ref.name)) {
438
+ seenEnvVars.add(ref.name);
439
+ warnings.push({
440
+ category: "signing-config",
441
+ severity: "warning",
442
+ message: `Signing credential referenced: ${ref.name}`,
443
+ hint: `Add via: ci secrets add ${ref.name}`,
444
+ location: `${rel}:${ref.line}`,
445
+ });
446
+ }
447
+ }
448
+ }
449
+ // System.getenv() references (outside buildConfigField — caught separately)
450
+ for (const ref of extractEnvVarRefs(content)) {
451
+ if (!seenEnvVars.has(ref.name)) {
452
+ seenEnvVars.add(ref.name);
453
+ warnings.push({
454
+ category: "env-var",
455
+ severity: "warning",
456
+ message: `Environment variable referenced: ${ref.name}`,
457
+ hint: `Add via: ci secrets add ${ref.name}`,
458
+ location: `${rel}:${ref.line}`,
459
+ });
460
+ }
461
+ }
462
+ // buildConfigField + System.getenv references
463
+ for (const ref of extractBuildConfigEnvRefs(content)) {
464
+ if (!seenEnvVars.has(ref.name)) {
465
+ seenEnvVars.add(ref.name);
466
+ warnings.push({
467
+ category: "build-config",
468
+ severity: "warning",
469
+ message: `BuildConfig env var referenced: ${ref.name}`,
470
+ hint: `Add via: ci secrets add ${ref.name}`,
471
+ location: `${rel}:${ref.line}`,
472
+ });
473
+ }
474
+ }
475
+ // Gradle property references
476
+ for (const ref of extractPropertyRefs(content)) {
477
+ if (!seenProperties.has(ref.name)) {
478
+ seenProperties.add(ref.name);
479
+ warnings.push({
480
+ category: "gradle-property",
481
+ severity: "warning",
482
+ message: `Gradle property referenced: ${ref.name}`,
483
+ hint: "Define in gradle.properties or pass as -P flag on CI",
484
+ location: `${rel}:${ref.line}`,
485
+ });
486
+ }
487
+ }
488
+ }
489
+ // ------------------------------------------------------------------
490
+ // 4. Google Secrets Gradle Plugin — detect missing local.properties
491
+ // ------------------------------------------------------------------
492
+ //
493
+ // The Secrets plugin reads a properties file (default: local.properties) and
494
+ // auto-generates BuildConfig fields. If that file is gitignored / absent in CI
495
+ // the fields are not generated, causing "Unresolved reference" compile errors.
496
+ // We check once per unique properties file path across all modules.
497
+ // ------------------------------------------------------------------
498
+ const seenSecretsPropertiesFiles = new Set();
499
+ // First pass: collect the defaultPropertiesFileName from any build file that sets it.
500
+ // This is typically "secrets.defaults.properties" in the app module.
501
+ let secretsDefaultsFileName;
502
+ for (const filePath of gradleFiles) {
503
+ const content = safeRead(filePath);
504
+ const block = extractBlockContent(content, "secrets");
505
+ if (!block)
506
+ continue;
507
+ const m = /defaultPropertiesFileName\s*=\s*["']([^"']+)["']/.exec(block);
508
+ if (m) {
509
+ secretsDefaultsFileName = m[1];
510
+ break;
511
+ }
512
+ }
513
+ // Also look for any *.defaults.properties file in the project root as a fallback
514
+ if (!secretsDefaultsFileName) {
515
+ const candidates = ["secrets.defaults.properties", "local.defaults.properties"];
516
+ for (const c of candidates) {
517
+ if (existsSync(resolve(projectRoot, c))) {
518
+ secretsDefaultsFileName = c;
519
+ break;
520
+ }
521
+ }
522
+ }
523
+ for (const filePath of gradleFiles) {
524
+ const content = safeRead(filePath);
525
+ if (!detectSecretsPlugin(content))
526
+ continue;
527
+ const propsFileName = extractSecretsPropertiesFile(content);
528
+ if (seenSecretsPropertiesFiles.has(propsFileName))
529
+ continue;
530
+ seenSecretsPropertiesFiles.add(propsFileName);
531
+ // Warn when the properties file is gitignored — it won't be present on a clean CI machine
532
+ // even if it exists locally (e.g. local.properties with only sdk.dir).
533
+ const isGitignored = gitignoreItems.some((item) => item.includes(propsFileName));
534
+ if (!isGitignored)
535
+ continue;
536
+ // Find expected keys from the companion defaults file
537
+ const expectedKeys = secretsDefaultsFileName
538
+ ? parsePropertyKeys(safeRead(resolve(projectRoot, secretsDefaultsFileName)))
539
+ : [];
540
+ if (expectedKeys.length > 0) {
541
+ // Emit one warning per expected key so it shows up in the missing-secrets hint
542
+ for (const key of expectedKeys) {
543
+ warnings.push({
544
+ category: "secrets-plugin",
545
+ severity: "warning",
546
+ message: `Secrets plugin key: ${key}`,
547
+ hint: `Add via: ci secrets add ${key}`,
548
+ location: propsFileName,
549
+ });
550
+ }
551
+ }
552
+ else {
553
+ warnings.push({
554
+ category: "secrets-plugin",
555
+ severity: "warning",
556
+ message: `Google Secrets Gradle Plugin reads '${propsFileName}' which is absent in CI`,
557
+ hint: `Inject '${propsFileName}' as a file step using a secret variable`,
558
+ location: relPath(projectRoot, filePath),
559
+ });
560
+ }
561
+ }
562
+ // ------------------------------------------------------------------
563
+ // 5. Firebase / GMS — check google-services.json after scanning all files
564
+ // ------------------------------------------------------------------
565
+ if (gmsDetected) {
566
+ // google-services.json can live in the root or in the app module dir
567
+ const candidatePaths = [
568
+ resolve(projectRoot, "google-services.json"),
569
+ resolve(projectRoot, "app", "google-services.json"),
570
+ ];
571
+ const found = candidatePaths.some((p) => existsSync(p));
572
+ if (!found) {
573
+ warnings.push({
574
+ category: "firebase",
575
+ severity: "warning",
576
+ message: `google-services.json not found (GMS plugin detected in ${gmsDetectedIn})`,
577
+ hint: "Add google-services.json as a secret file or generate it from env vars at build time",
578
+ });
579
+ }
580
+ }
581
+ // ------------------------------------------------------------------
582
+ // 5. Signing config summary warning (if detected and not already specific)
583
+ // ------------------------------------------------------------------
584
+ if (signingDetected) {
585
+ warnings.push({
586
+ category: "signing-config",
587
+ severity: "info",
588
+ message: `signingConfigs block found in ${signingDetectedIn}`,
589
+ hint: "Ensure keystore file and all signing credentials are available in the CI environment",
590
+ });
591
+ }
592
+ // ------------------------------------------------------------------
593
+ // 6. Gitignore cross-check
594
+ // ------------------------------------------------------------------
595
+ for (const item of gitignoreItems) {
596
+ // Only warn if we haven't already emitted a more specific warning for it
597
+ const alreadyCovered = (item === "google-services.json" && gmsDetected) ||
598
+ (item === "local.properties" && !existsSync(resolve(projectRoot, "local.properties")));
599
+ if (!alreadyCovered) {
600
+ warnings.push({
601
+ category: "missing-file",
602
+ severity: "info",
603
+ message: `${item} is in .gitignore — likely absent in CI`,
604
+ hint: "Provide this file as a secret or generate it at build time",
605
+ });
606
+ }
607
+ }
608
+ return { warnings, detectedJavaVersion, buildVariants };
609
+ }
610
+ // ---------------------------------------------------------------------------
611
+ // Formatter
612
+ // ---------------------------------------------------------------------------
613
+ const CATEGORY_LABELS = {
614
+ "missing-file": "Missing files",
615
+ "env-var": "Environment variables referenced in Gradle files",
616
+ "gradle-property": "Gradle properties referenced",
617
+ "signing-config": "Signing configuration",
618
+ "build-config": "BuildConfig env vars",
619
+ "firebase": "Firebase / GMS",
620
+ "secrets-plugin": "Google Secrets Gradle Plugin (local.properties keys)",
621
+ };
622
+ export function formatScanResult(result) {
623
+ const lines = [];
624
+ const sep = "─".repeat(50);
625
+ // Always show the detected Java version
626
+ if (result.detectedJavaVersion !== undefined) {
627
+ lines.push(`ℹ Detected Java ${result.detectedJavaVersion} requirement — pipeline will use Java ${result.detectedJavaVersion}`);
628
+ lines.push("");
629
+ }
630
+ if (result.warnings.length === 0) {
631
+ lines.push("✅ No potential unknowns detected.");
632
+ return lines.join("\n") + "\n";
633
+ }
634
+ lines.push(`⚠ Android Project Scan — Potential Unknowns`);
635
+ lines.push(sep);
636
+ lines.push("");
637
+ // Group by category
638
+ const byCategory = new Map();
639
+ for (const w of result.warnings) {
640
+ if (!byCategory.has(w.category))
641
+ byCategory.set(w.category, []);
642
+ byCategory.get(w.category).push(w);
643
+ }
644
+ for (const [cat, items] of byCategory) {
645
+ lines.push(`${CATEGORY_LABELS[cat]}:`);
646
+ for (const w of items) {
647
+ const icon = w.severity === "warning" ? "⚠ " : "ℹ ";
648
+ const loc = w.location ? ` (${w.location})` : "";
649
+ lines.push(` ${icon} ${w.message}${loc}`);
650
+ if (w.hint) {
651
+ lines.push(` Hint: ${w.hint}`);
652
+ }
653
+ }
654
+ lines.push("");
655
+ }
656
+ lines.push(sep);
657
+ const warnCount = result.warnings.filter((w) => w.severity === "warning").length;
658
+ const infoCount = result.warnings.filter((w) => w.severity === "info").length;
659
+ const parts = [];
660
+ if (warnCount > 0)
661
+ parts.push(`${warnCount} warning(s)`);
662
+ if (infoCount > 0)
663
+ parts.push(`${infoCount} info`);
664
+ lines.push(`${parts.join(", ")} found. Review before running on CI.`);
665
+ return lines.join("\n");
666
+ }
667
+ //# sourceMappingURL=android-scanner.js.map
@@ -0,0 +1,5 @@
1
+ export declare function handleBuildCommand(detectMobileProjectRoot: (dir: string) => "android" | "ios" | null, options?: {
2
+ createPipelinesDir?: boolean;
3
+ nonInteractive?: boolean;
4
+ }): Promise<void>;
5
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"AAw9BA,wBAAsB,kBAAkB,CACtC,uBAAuB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,GAAG,KAAK,GAAG,IAAI,EAClE,OAAO,GAAE;IAAE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAAC,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GACvE,OAAO,CAAC,IAAI,CAAC,CAqRf"}