@invarn/cibuild 2.7.9 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/cli.cjs +143 -10
  2. package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.d.ts +2 -0
  3. package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.d.ts.map +1 -0
  4. package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.js +218 -0
  5. package/dist/src/commands/android-java-version.test.js +93 -10
  6. package/dist/src/commands/android-scanner.d.ts +109 -8
  7. package/dist/src/commands/android-scanner.d.ts.map +1 -1
  8. package/dist/src/commands/android-scanner.js +397 -27
  9. package/dist/src/commands/build.d.ts +8 -0
  10. package/dist/src/commands/build.d.ts.map +1 -1
  11. package/dist/src/commands/build.js +17 -3
  12. package/dist/src/commands/index.d.ts +1 -0
  13. package/dist/src/commands/index.d.ts.map +1 -1
  14. package/dist/src/commands/index.js +3 -0
  15. package/dist/src/commands/ios-scanner.d.ts.map +1 -1
  16. package/dist/src/commands/ios-scanner.js +15 -24
  17. package/dist/src/commands/ios-scheme-ranking.test.js +5 -0
  18. package/dist/src/shared/detect-project.d.ts +32 -0
  19. package/dist/src/shared/detect-project.d.ts.map +1 -1
  20. package/dist/src/shared/detect-project.js +62 -10
  21. package/dist/src/shared/xcode-container.test.d.ts +2 -0
  22. package/dist/src/shared/xcode-container.test.d.ts.map +1 -0
  23. package/dist/src/shared/xcode-container.test.js +123 -0
  24. package/dist/src/yaml/steps/xcode-app-product.d.ts +90 -0
  25. package/dist/src/yaml/steps/xcode-app-product.d.ts.map +1 -0
  26. package/dist/src/yaml/steps/xcode-app-product.js +247 -0
  27. package/dist/src/yaml/steps/xcode-app-product.test.d.ts +2 -0
  28. package/dist/src/yaml/steps/xcode-app-product.test.d.ts.map +1 -0
  29. package/dist/src/yaml/steps/xcode-app-product.test.js +202 -0
  30. package/dist/src/yaml/steps/xcode-derived-data.test.js +66 -1
  31. package/dist/src/yaml/steps/xcode.d.ts.map +1 -1
  32. package/dist/src/yaml/steps/xcode.js +50 -3
  33. package/package.json +1 -1
@@ -138,6 +138,259 @@ function extractSecretsPropertiesFile(content) {
138
138
  const m = /propertiesFileName\s*=\s*["']([^"']+)["']/.exec(block);
139
139
  return m ? m[1] : "local.properties";
140
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
+ // Bounds on the source walk, the way `findGradleFiles` bounds itself to the
320
+ // files a build actually declares. A repository is an unbounded tree and this
321
+ // runs on every scan of one.
322
+ const SOURCE_EXTENSIONS = [".kt", ".kts", ".java"];
323
+ const SOURCE_SKIP_DIRS = new Set(["build", "node_modules"]);
324
+ const SOURCE_FILE_CAP = 1500;
325
+ const SOURCE_DEPTH_CAP = 12;
326
+ /** Source files under `dir`, up to the shared file budget. */
327
+ function findSourceFiles(dir, budget, depth = 0) {
328
+ if (depth > SOURCE_DEPTH_CAP || budget.left <= 0)
329
+ return [];
330
+ let entries;
331
+ try {
332
+ entries = readdirSync(dir, { withFileTypes: true });
333
+ }
334
+ catch {
335
+ return [];
336
+ }
337
+ const files = [];
338
+ const subdirs = [];
339
+ for (const entry of entries) {
340
+ // Dot directories hold caches and VCS state, never source: `.git`,
341
+ // `.gradle`, `.idea`, `.kotlin`.
342
+ if (entry.name.startsWith("."))
343
+ continue;
344
+ if (entry.isDirectory()) {
345
+ if (!SOURCE_SKIP_DIRS.has(entry.name))
346
+ subdirs.push(join(dir, entry.name));
347
+ }
348
+ else if (SOURCE_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {
349
+ if (budget.left <= 0)
350
+ break;
351
+ budget.left--;
352
+ files.push(join(dir, entry.name));
353
+ }
354
+ }
355
+ for (const sub of subdirs)
356
+ files.push(...findSourceFiles(sub, budget, depth + 1));
357
+ return files;
358
+ }
359
+ /** The directories of `dirs` that no other member of `dirs` contains. */
360
+ function outermost(dirs) {
361
+ return dirs.filter((dir) => !dirs.some((other) => other !== dir && dir.startsWith(other + "/")));
362
+ }
363
+ /**
364
+ * The property names the build reads out of the Secrets plugin's properties
365
+ * file — see `ScanResult.missingPropertyKeys`.
366
+ *
367
+ * Bounded to the modules that apply the plugin, because that is where its
368
+ * generated fields are visible and where the references to them live.
369
+ */
370
+ function collectPropertyKeys(gradleFiles) {
371
+ // A module with both a `build.gradle` and a `build.gradle.kts` yields its
372
+ // directory twice, and walking it twice would spend the file budget twice.
373
+ const moduleDirs = new Set();
374
+ const keys = new Set();
375
+ for (const filePath of gradleFiles) {
376
+ const content = safeRead(filePath);
377
+ if (!appliesSecretsPlugin(content))
378
+ continue;
379
+ moduleDirs.add(dirname(filePath));
380
+ for (const key of extractManifestPlaceholderKeys(content))
381
+ keys.add(key);
382
+ }
383
+ if (moduleDirs.size === 0)
384
+ return [];
385
+ const budget = { left: SOURCE_FILE_CAP };
386
+ for (const dir of outermost([...moduleDirs])) {
387
+ for (const file of findSourceFiles(dir, budget)) {
388
+ for (const name of extractBuildConfigFieldRefs(safeRead(file)))
389
+ keys.add(name);
390
+ }
391
+ }
392
+ return [...keys].sort();
393
+ }
141
394
  /** Parse property keys from a standard .properties file, skipping comments and blank lines. */
142
395
  function parsePropertyKeys(propertiesContent) {
143
396
  return propertiesContent
@@ -518,12 +771,87 @@ export function gradleJavaFloor(gradleVersion) {
518
771
  */
519
772
  export const AVAILABLE_JAVA_VERSIONS = [8, 11, 17, 21, 25];
520
773
  /**
521
- * The newest JDK a build machine carries. Named because it is what a
522
- * generated pipeline falls back to when no JDK can satisfy the constraints —
523
- * a pipeline has to write something, and `javaVersionProblem` says what was
524
- * given up.
774
+ * The newest JDK a build machine carries.
775
+ *
776
+ * It is no longer what the chooser falls back to. It used to be: with no JDK
777
+ * satisfying both the sources and the wrapper, `runnableJavaVersion` returned
778
+ * this — which on a project pinning a modern Java level under an old Gradle
779
+ * wrapper is a JDK **above** that wrapper's ceiling, and the build dies at
780
+ * `:app:processDebugMainManifest` with "module java.base does not opens
781
+ * java.io to unnamed module". A number above the ceiling is not a fallback;
782
+ * it is the failure, chosen deliberately and reported as success.
525
783
  */
526
784
  export const NEWEST_AVAILABLE_JAVA_VERSION = Math.max(...AVAILABLE_JAVA_VERSIONS);
785
+ /**
786
+ * The JDK to build on, with the reason attached — one place where the
787
+ * constraints are resolved, so the answer and the explanation cannot disagree.
788
+ *
789
+ * They used to. The chooser fell back to the newest installed JDK whenever
790
+ * nothing satisfied both constraints, and the sentence beside it had two
791
+ * branches — a ceiling below every installed JDK, and a declaration above
792
+ * every installed JDK — so the commonest shape of all fell through both and
793
+ * said nothing at all. A project pinning Java 12 against a Gradle wrapper that
794
+ * runs on 11 at most got Java 25 and silence.
795
+ *
796
+ * Three facts decide it:
797
+ *
798
+ * - **What the build machine has.** Only `AVAILABLE_JAVA_VERSIONS` exist, so
799
+ * a project declaring 30 cannot simply be given 30.
800
+ * - **What Gradle can run on.** The wrapper sets a ceiling that source
801
+ * compatibility cannot raise: Gradle starts before it reads a single
802
+ * `sourceCompatibility`.
803
+ * - **What the sources ask for.** A declaration is a floor, not an exact
804
+ * request — a project declaring 11 compiles fine on 21.
805
+ *
806
+ * `kind` distinguishes the five outcomes:
807
+ *
808
+ * - `unknown` — nothing declared and no readable wrapper. Nothing to go on
809
+ * and nothing to say.
810
+ * - `satisfies` — an installed JDK meets both the floor and the ceiling.
811
+ * - `capped-by-wrapper` — nothing meets both, and the wrapper is why. The
812
+ * ceiling is hard and the declaration is a preference, so the newest JDK
813
+ * the wrapper *can* run is the answer, and the caller says so.
814
+ * - `above-runners` — the declaration is above every installed JDK, and that
815
+ * rather than the wrapper is what binds. **No answer**: asking for it would
816
+ * fail when the JDK is selected, and quietly picking a lower one would be
817
+ * the guess this exists to avoid.
818
+ * - `wrapper-below-runners` — the ceiling is below every installed JDK. No
819
+ * answer exists.
820
+ *
821
+ * `above-runners` is checked before `capped-by-wrapper` so a project declaring
822
+ * 99 against a modern wrapper is told about the machines rather than about its
823
+ * wrapper: both bind, and the one it can act on is the one worth naming.
824
+ */
825
+ export function chooseJavaVersion(detectedJavaVersion, gradleVersion) {
826
+ const declared = Number(detectedJavaVersion) || 0;
827
+ const ceiling = gradleJavaCeiling(gradleVersion);
828
+ const floor = gradleJavaFloor(gradleVersion);
829
+ const installed = [...AVAILABLE_JAVA_VERSIONS].sort((a, b) => a - b);
830
+ const base = { declared, ceiling, installed };
831
+ if (!declared && floor === undefined) {
832
+ return { ...base, version: undefined, kind: "unknown" };
833
+ }
834
+ const wanted = Math.max(declared, floor ?? 0);
835
+ const usable = installed.filter((v) => v >= wanted && v <= (ceiling ?? Infinity));
836
+ // The newest that fits. Nothing reaches the oldest installed JDK unless a
837
+ // ceiling leaves nothing else, which is the only reason 8 and 11 are on the
838
+ // images at all.
839
+ if (usable.length > 0) {
840
+ return { ...base, version: usable[usable.length - 1], kind: "satisfies" };
841
+ }
842
+ if (declared > installed[installed.length - 1]) {
843
+ return { ...base, version: undefined, kind: "above-runners" };
844
+ }
845
+ const underCeiling = installed.filter((v) => v <= (ceiling ?? Infinity));
846
+ if (underCeiling.length > 0) {
847
+ return {
848
+ ...base,
849
+ version: underCeiling[underCeiling.length - 1],
850
+ kind: "capped-by-wrapper",
851
+ };
852
+ }
853
+ return { ...base, version: undefined, kind: "wrapper-below-runners" };
854
+ }
527
855
  /**
528
856
  * The Java version to write into a generated pipeline: the **newest**
529
857
  * available JDK that satisfies both what the sources declare and what the
@@ -542,34 +870,44 @@ export const NEWEST_AVAILABLE_JAVA_VERSION = Math.max(...AVAILABLE_JAVA_VERSIONS
542
870
  * above had to learn minor versions before this could ship.
543
871
  */
544
872
  export function runnableJavaVersion(detectedJavaVersion, gradleVersion) {
545
- const ceiling = gradleJavaCeiling(gradleVersion) ?? Infinity;
546
- const floor = Math.max(detectedJavaVersion ?? 0, gradleJavaFloor(gradleVersion) ?? 0);
547
- const usable = AVAILABLE_JAVA_VERSIONS.filter((v) => v >= floor && v <= ceiling).sort((a, b) => a - b);
548
- if (usable.length === 0)
549
- return NEWEST_AVAILABLE_JAVA_VERSION;
550
- return usable[usable.length - 1];
873
+ return chooseJavaVersion(detectedJavaVersion, gradleVersion).version;
551
874
  }
552
875
  /**
553
- * What `runnableJavaVersion` could not honour, as a sentence, or undefined
554
- * when it honoured everything.
876
+ * What the choice could not honour, as a sentence, or undefined when it
877
+ * honoured everything.
878
+ *
879
+ * Silence is the failure this exists to prevent: a pipeline that quietly
880
+ * overruled what a project asked for reads as the project's own problem, and
881
+ * the commonest shape of all — an old wrapper under a modern declaration — was
882
+ * the one that used to fall through every branch.
883
+ *
884
+ * `source` names the file that asked, when the caller knows it. The scan now
885
+ * returns it, so "asks for Java 12" can become "asks for Java 12
886
+ * (.github/workflows/Build.yml)" — a version with no provenance sends a reader
887
+ * looking through every Gradle file for a number that is not in any of them.
555
888
  */
556
- export function javaVersionProblem(detectedJavaVersion, gradleVersion) {
889
+ export function javaVersionProblem(detectedJavaVersion, gradleVersion, source) {
890
+ const choice = chooseJavaVersion(detectedJavaVersion, gradleVersion);
891
+ if (choice.kind === "satisfies" || choice.kind === "unknown")
892
+ return undefined;
893
+ const { declared, ceiling, installed } = choice;
557
894
  // "11, 17 and 21", not "11 and 17 and 21" — a human reads this next to a
558
895
  // build that will not start.
559
- const sorted = [...AVAILABLE_JAVA_VERSIONS].sort((a, b) => a - b);
560
- const installed = sorted.length > 1
561
- ? `${sorted.slice(0, -1).join(", ")} and ${sorted[sorted.length - 1]}`
562
- : String(sorted[0]);
563
- const ceiling = gradleJavaCeiling(gradleVersion);
564
- const lowest = Math.min(...AVAILABLE_JAVA_VERSIONS);
565
- if (ceiling !== undefined && ceiling < lowest) {
566
- return `Gradle ${gradleVersion} runs on Java ${ceiling} at most, and build machines carry only ${installed}. This pipeline will very likely fail before it compiles anything — upgrade the Gradle wrapper.`;
896
+ const list = installed.length > 1
897
+ ? `${installed.slice(0, -1).join(", ")} and ${installed[installed.length - 1]}`
898
+ : String(installed[0]);
899
+ const asked = source ? `Java ${declared} (${source})` : `Java ${declared}`;
900
+ if (choice.kind === "wrapper-below-runners") {
901
+ 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.`;
567
902
  }
568
- const highest = Math.max(...AVAILABLE_JAVA_VERSIONS);
569
- if ((detectedJavaVersion ?? 0) > highest) {
570
- return `This project declares Java ${detectedJavaVersion}, and build machines carry only ${installed}. Writing Java ${runnableJavaVersion(detectedJavaVersion, gradleVersion)} instead — asking for ${detectedJavaVersion} would fail at "Set the Java version".`;
903
+ if (choice.kind === "above-runners") {
904
+ 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.`;
571
905
  }
572
- return undefined;
906
+ // capped-by-wrapper
907
+ const preamble = declared
908
+ ? `This project asks for ${asked}, and Gradle ${gradleVersion} runs on Java ${ceiling} at most.`
909
+ : `Gradle ${gradleVersion} runs on Java ${ceiling} at most.`;
910
+ return `${preamble} Building on ${choice.version} — the newest the wrapper allows.`;
573
911
  }
574
912
  // ---------------------------------------------------------------------------
575
913
  // Gitignore helper
@@ -817,6 +1155,12 @@ export async function scanAndroidProject(projectRoot) {
817
1155
  const detectedJavaVersion = declaredJavaVersion === undefined && pinnedJava === undefined
818
1156
  ? undefined
819
1157
  : Math.max(declaredJavaVersion ?? 0, pinnedJava?.version ?? 0);
1158
+ // Only when the pin is what set the level. A Gradle file asking for as much
1159
+ // or more is the thing a reader would find by looking, so naming the
1160
+ // workflow there would point away from the answer.
1161
+ const detectedJavaVersionSource = pinnedJava !== undefined && pinnedJava.version > (declaredJavaVersion ?? 0)
1162
+ ? pinnedJava.source
1163
+ : undefined;
820
1164
  const detectedGradleVersion = detectGradleWrapperVersion(projectRoot);
821
1165
  const buildVariants = detectBuildVariants(gradleFiles);
822
1166
  // ------------------------------------------------------------------
@@ -979,6 +1323,16 @@ export async function scanAndroidProject(projectRoot) {
979
1323
  }
980
1324
  }
981
1325
  // ------------------------------------------------------------------
1326
+ // 4b. Which keys that properties file has to declare
1327
+ // ------------------------------------------------------------------
1328
+ //
1329
+ // Section 4 says the file is absent; this says what is in it. The two are
1330
+ // separate because they answer different questions for different readers —
1331
+ // the warnings above tell a person a file is missing, and this tells a
1332
+ // generator what a stand-in for it has to contain.
1333
+ // ------------------------------------------------------------------
1334
+ const missingPropertyKeys = collectPropertyKeys(gradleFiles);
1335
+ // ------------------------------------------------------------------
982
1336
  // 5. Firebase / GMS — check google-services.json after scanning all files
983
1337
  // ------------------------------------------------------------------
984
1338
  if (gmsDetected) {
@@ -1024,7 +1378,14 @@ export async function scanAndroidProject(projectRoot) {
1024
1378
  });
1025
1379
  }
1026
1380
  }
1027
- return { warnings, detectedJavaVersion, detectedGradleVersion, buildVariants };
1381
+ return {
1382
+ warnings,
1383
+ detectedJavaVersion,
1384
+ detectedJavaVersionSource,
1385
+ detectedGradleVersion,
1386
+ buildVariants,
1387
+ missingPropertyKeys,
1388
+ };
1028
1389
  }
1029
1390
  // ---------------------------------------------------------------------------
1030
1391
  // Formatter
@@ -1045,9 +1406,18 @@ export function formatScanResult(result) {
1045
1406
  // actually name — they differ whenever the declaration is below the oldest
1046
1407
  // installed JDK or above the newest, and saying only the first made the
1047
1408
  // second look like it had been honoured.
1409
+ //
1410
+ // And when they differ, say why in the same breath. An answer that is not
1411
+ // what was asked for, printed with no reason, reads as a fact about the
1412
+ // project rather than as a decision made on its behalf.
1048
1413
  if (result.detectedJavaVersion !== undefined) {
1049
1414
  const willUse = runnableJavaVersion(result.detectedJavaVersion, result.detectedGradleVersion);
1050
- lines.push(`ℹ Detected Java ${result.detectedJavaVersion} requirement — pipeline will use Java ${willUse}`);
1415
+ lines.push(willUse === undefined
1416
+ ? `ℹ Detected Java ${result.detectedJavaVersion} requirement — no installed JDK can satisfy it`
1417
+ : `ℹ Detected Java ${result.detectedJavaVersion} requirement — pipeline will use Java ${willUse}`);
1418
+ const problem = javaVersionProblem(result.detectedJavaVersion, result.detectedGradleVersion, result.detectedJavaVersionSource);
1419
+ if (problem)
1420
+ lines.push(` ${problem}`);
1051
1421
  lines.push("");
1052
1422
  }
1053
1423
  if (result.warnings.length === 0) {
@@ -197,6 +197,14 @@ export declare function detectSetupNeeds(warnings: BuildWarning[]): Omit<SetupOp
197
197
  * and a webhook must never reach it.
198
198
  */
199
199
  export declare function triggerMapBlock(defaultBranch?: string): string;
200
+ /**
201
+ * The JDK a generated pipeline names when the project's own constraints have
202
+ * no answer — nothing declared and no readable wrapper, or a declaration no
203
+ * installed JDK can meet. A generated pipeline has to write something; what it
204
+ * must not do is write a number the wrapper cannot start on and call that a
205
+ * choice.
206
+ */
207
+ export declare const DEFAULT_PIPELINE_JAVA_VERSION = 17;
200
208
  export declare function generateAndroidPipeline(javaVersion?: number, setup?: SetupOptions, variants?: WorkflowVariants, cacheTechnology?: "gradle" | "kmm", metaNamespace?: 'invarn' | 'cibuild.io', defaultBranch?: string): string;
201
209
  export declare function generateIosPipeline(projectPath: string, setup: IosSetupOptions, variants: IosWorkflowVariants, metaNamespace?: 'invarn' | 'cibuild.io', defaultBranch?: string): string;
202
210
  export declare function handleBuildCommand(detectMobileProjectRoot: (dir: string) => "android" | "ios" | "kmm" | null, options?: {
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,sBAAsB,CAAC;AAMrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAIhE,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,oFAAoF;IACpF,YAAY,EAAE,KAAK,GAAG,KAAK,CAAC;IAC5B,8FAA8F;IAC9F,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC;;;;;;;;;;;;;;OAcG;IACH,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C;;;;;;;;;;;OAWG;IACH,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/C;;;;;OAKG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAC3B,oFAAoF;IACpF,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,+FAA+F;IAC/F,SAAS,EAAE,MAAM,CAAC;IAClB,6FAA6F;IAC7F,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,qBAAqB,CAAC;IAC/B,WAAW,EAAE,qBAAqB,CAAC;IACnC,OAAO,EAAE,qBAAqB,CAAC;CAChC;AAiBD,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,uBAAuB,CAAC;IACjC,WAAW,EAAE,uBAAuB,CAAC;IACrC,OAAO,EAAE,uBAAuB,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAiID;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,MAAM,GAAG,IAAI,CAYjF;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CASxF;AAED,6EAA6E;AAC7E,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,SAAQ,GAAG,MAAM,CAInF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,gBAAgB,EAC1B,cAAc,EAAE,MAAM,EAAE,EACxB,MAAM,SAAQ,GACb,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAED,uFAAuF;AACvF,eAAO,MAAM,mBAAmB,iDAAkD,CAAC;AAWnF;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,gBAAgB,EAC1B,cAAc,EAAE,MAAM,EAAE,GACvB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAM/B;AAED,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GACjE,cAAc,EAAE,CAOlB;AAgBD,yFAAyF;AACzF,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,sFAAsF;IACtF,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/C,KAAK,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,MAAM,GAC5D,eAAe,EAAE,CAUnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAC7C,eAAe,EAAE,CAInB;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,YAAY,EAAE,GACvB,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,qBAAqB,GAAG,iBAAiB,CAAC,CAoBjF;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,aAAa,GAAE,MAAgC,GAAG,MAAM,CAcvF;AAED;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD,wBAAgB,uBAAuB,CACrC,WAAW,GAAE,MAAsC,EACnD,KAAK,GAAE,YAAmS,EAC1S,QAAQ,GAAE,gBAAmC,EAC7C,eAAe,GAAE,QAAQ,GAAG,KAAgB,EAC5C,aAAa,GAAE,QAAQ,GAAG,YAA2B,EACrD,aAAa,GAAE,MAAgC,GAC9C,MAAM,CAyTR;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,mBAAmB,EAC7B,aAAa,GAAE,QAAQ,GAAG,YAA2B,EACrD,aAAa,GAAE,MAAgC,GAC9C,MAAM,CA4LR;AAiVD,wBAAsB,kBAAkB,CACtC,uBAAuB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI,EAC1E,OAAO,GAAE;IACP,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;CACpC,GACL,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
@@ -326,7 +326,15 @@ trigger_map:
326
326
  workflow: pull-request
327
327
  `;
328
328
  }
329
- export function generateAndroidPipeline(javaVersion = 17, setup = { keystore: false, keystoreProperties: false, googleServices: false, googlePlayDeploy: false, googlePlayPackageName: '', artifactType: 'apk', keystorePaths: {}, googleServicesPaths: {}, workflowFlavors: {}, secretsProperties: false, secretsPropertiesPath: 'local.properties' }, variants = DEFAULT_VARIANTS, cacheTechnology = "gradle", metaNamespace = 'cibuild.io', defaultBranch = FALLBACK_DEFAULT_BRANCH) {
329
+ /**
330
+ * The JDK a generated pipeline names when the project's own constraints have
331
+ * no answer — nothing declared and no readable wrapper, or a declaration no
332
+ * installed JDK can meet. A generated pipeline has to write something; what it
333
+ * must not do is write a number the wrapper cannot start on and call that a
334
+ * choice.
335
+ */
336
+ export const DEFAULT_PIPELINE_JAVA_VERSION = 17;
337
+ export function generateAndroidPipeline(javaVersion = DEFAULT_PIPELINE_JAVA_VERSION, setup = { keystore: false, keystoreProperties: false, googleServices: false, googlePlayDeploy: false, googlePlayPackageName: '', artifactType: 'apk', keystorePaths: {}, googleServicesPaths: {}, workflowFlavors: {}, secretsProperties: false, secretsPropertiesPath: 'local.properties' }, variants = DEFAULT_VARIANTS, cacheTechnology = "gradle", metaNamespace = 'cibuild.io', defaultBranch = FALLBACK_DEFAULT_BRANCH) {
330
338
  const keystoreStepFor = (wf) => {
331
339
  if (!setup.keystore)
332
340
  return "";
@@ -1370,11 +1378,17 @@ async function handleAndroidBuildCommand(cwd, options, cacheTechnology) {
1370
1378
  }
1371
1379
  }
1372
1380
  // 7. Generate and write the pipeline
1381
+ //
1382
+ // The chooser abstains rather than falling back, so `undefined` here means
1383
+ // "no installed JDK meets this project's constraints" — not "17 is fine".
1384
+ // The default is what gets written in that case, and the warning below is
1385
+ // what says so; it is never silent when the answer is not what was asked
1386
+ // for, which is the whole reason the two are resolved together.
1373
1387
  const javaVersion = runnableJavaVersion(scanResult.detectedJavaVersion, scanResult.detectedGradleVersion);
1374
- const javaProblem = javaVersionProblem(scanResult.detectedJavaVersion, scanResult.detectedGradleVersion);
1388
+ const javaProblem = javaVersionProblem(scanResult.detectedJavaVersion, scanResult.detectedGradleVersion, scanResult.detectedJavaVersionSource);
1375
1389
  if (javaProblem)
1376
1390
  console.log(`\n⚠️ ${javaProblem}`);
1377
- const yaml = generateAndroidPipeline(javaVersion, setupOptions, variants, cacheTechnology, options.metaNamespace, detectDefaultBranch());
1391
+ const yaml = generateAndroidPipeline(javaVersion ?? DEFAULT_PIPELINE_JAVA_VERSION, setupOptions, variants, cacheTechnology, options.metaNamespace, detectDefaultBranch());
1378
1392
  writeFileSync(outputPath, yaml, "utf-8");
1379
1393
  console.log("\n✅ Generated .ci/pipelines/cibuild.yml");
1380
1394
  console.log(` Platform: ${platformLabel}`);
@@ -23,6 +23,7 @@ export { handleBuildCommand } from "./build.js";
23
23
  export { handleResetCommand } from "./reset.js";
24
24
  export { detectMobileProjectRoot } from "../shared/detect-project.js";
25
25
  export type { MobileProjectType } from "../shared/detect-project.js";
26
+ export { isXcodeContainer, xcodeContainersIn } from "../shared/detect-project.js";
26
27
  export { scanIosProject, formatIosScanResult } from "./ios-scanner.js";
27
28
  export type { IosScanResult, IosWarning, IosWarningCategory, IosWarningSeverity, } from "./ios-scanner.js";
28
29
  export { scanAndroidProject, formatScanResult, detectBuildVariants, } from "./android-scanner.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AACnE,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGhD,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAOrE,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvE,YAAY,EACV,aAAa,EACb,UAAU,EACV,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,UAAU,IAAI,iBAAiB,EAC/B,aAAa,EACb,YAAY,EACZ,eAAe,EACf,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACjF,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,aAAa,GACd,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AACnE,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGhD,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAGrE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAOlF,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvE,YAAY,EACV,aAAa,EACb,UAAU,EACV,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,UAAU,IAAI,iBAAiB,EAC/B,aAAa,EACb,YAAY,EACZ,eAAe,EACf,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACjF,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,aAAa,GACd,MAAM,4BAA4B,CAAC"}
@@ -19,6 +19,9 @@ export { handleBuildCommand } from "./build.js";
19
19
  export { handleResetCommand } from "./reset.js";
20
20
  // Helpers useful for callers that implement their own variations.
21
21
  export { detectMobileProjectRoot } from "../shared/detect-project.js";
22
+ // The one answer to "is this `.xcodeproj` a project", exported so the Invarn
23
+ // CLI's own scans cannot drift from cibuild's.
24
+ export { isXcodeContainer, xcodeContainersIn } from "../shared/detect-project.js";
22
25
  // Project scanners — pure, toolchain-free file-content detectors. Exposed
23
26
  // so external callers (the Invarn CLI) can detect build variables and
24
27
  // secret-bearing files from a checkout and push them to the backend,
@@ -1 +1 @@
1
- {"version":3,"file":"ios-scanner.d.ts","sourceRoot":"","sources":["../../../src/commands/ios-scanner.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,SAAS,GAAG,gBAAgB,GAAG,WAAW,CAAC;AAE7F,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB;;;OAGG;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,YAAY,EAAE,OAAO,CAAC;IACtB,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iFAAiF;IACjF,eAAe,EAAE,MAAM,CAAC;CACzB;AAuFD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,GAChB,MAAM,EAAE,CAEV;AAED,iEAAiE;AACjE,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,GAChB;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CA8CxC;AAmLD,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAgGhF;AAaD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAkDjE"}
1
+ {"version":3,"file":"ios-scanner.d.ts","sourceRoot":"","sources":["../../../src/commands/ios-scanner.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,SAAS,GAAG,gBAAgB,GAAG,WAAW,CAAC;AAE7F,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB;;;OAGG;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,YAAY,EAAE,OAAO,CAAC;IACtB,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iFAAiF;IACjF,eAAe,EAAE,MAAM,CAAC;CACzB;AAiFD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,GAChB,MAAM,EAAE,CAEV;AAED,iEAAiE;AACjE,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,GAChB;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CA8CxC;AAkLD,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAgGhF;AAaD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAkDjE"}