peachsim 0.1.668 → 0.1.670

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 (116) hide show
  1. package/cli/commands/control.ts +8 -5
  2. package/cli/commands/inspect/core.ts +7 -5
  3. package/cli/commands/inspect/wait-selector.ts +2 -11
  4. package/cli/commands/inspect.ts +6 -59
  5. package/cli/commands/kotlin.ts +6 -4
  6. package/cli/commands/skills.ts +28 -7
  7. package/cli/commands/upgrade.ts +18 -3
  8. package/cli/main.ts +21 -0
  9. package/cli/output-redaction.ts +182 -0
  10. package/cli/parse-args.ts +1 -0
  11. package/dist-lib/agent-daemon-client.cjs +1 -1
  12. package/dist-lib/agent-events.cjs +1 -1
  13. package/dist-lib/agent-identity.cjs +1 -1
  14. package/dist-lib/agent-sessions.cjs +1 -1
  15. package/dist-lib/attached-projects.cjs +1 -1
  16. package/dist-lib/auth/shared-session.cjs +1 -1
  17. package/dist-lib/backend-origin.cjs +1 -1
  18. package/dist-lib/beta.cjs +1 -1
  19. package/dist-lib/beta.mjs +1 -1
  20. package/dist-lib/bridge-constants.cjs +1 -1
  21. package/dist-lib/bridge-contract-input.cjs +1 -1
  22. package/dist-lib/bridge-contract-input.mjs +1 -1
  23. package/dist-lib/bridge-contract.cjs +1 -1
  24. package/dist-lib/bridge-contract.mjs +1 -1
  25. package/dist-lib/capture-contract.cjs +1 -1
  26. package/dist-lib/capture-contract.mjs +1 -1
  27. package/dist-lib/cli-constants.cjs +1 -1
  28. package/dist-lib/cloud-contract.cjs +1 -1
  29. package/dist-lib/cloud-contract.mjs +1 -1
  30. package/dist-lib/cloud-test-args.cjs +1 -1
  31. package/dist-lib/cloud-test-args.mjs +1 -1
  32. package/dist-lib/cloud.cjs +1 -1
  33. package/dist-lib/cloud.mjs +1 -1
  34. package/dist-lib/config.cjs +1 -1
  35. package/dist-lib/detox/index.cjs +1 -1
  36. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  37. package/dist-lib/home-paths.cjs +1 -1
  38. package/dist-lib/host/bridge-host.cjs +29 -19
  39. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  40. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  41. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  42. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  43. package/dist-lib/host/websocket-proxy.cjs +1 -1
  44. package/dist-lib/index.cjs +260 -17
  45. package/dist-lib/jump-to-source-babel.cjs +1 -1
  46. package/dist-lib/jump-to-source-native.cjs +1 -1
  47. package/dist-lib/kotlin-host.cjs +2 -1
  48. package/dist-lib/menu.cjs +1 -1
  49. package/dist-lib/menu.mjs +1 -1
  50. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  51. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  52. package/dist-lib/metro-production-bundle.cjs +1 -1
  53. package/dist-lib/metro-production-bundle.mjs +1 -1
  54. package/dist-lib/metro.cjs +259 -16
  55. package/dist-lib/native-app-identity.cjs +1 -1
  56. package/dist-lib/native-app-identity.mjs +1 -1
  57. package/dist-lib/native-firebase-metadata.cjs +115 -0
  58. package/dist-lib/native-firebase-metadata.mjs +86 -0
  59. package/dist-lib/plist.cjs +1 -1
  60. package/dist-lib/plist.mjs +1 -1
  61. package/dist-lib/profiles.cjs +1 -1
  62. package/dist-lib/public-brand.cjs +1 -1
  63. package/dist-lib/react-native-host-modules.cjs +1 -1
  64. package/dist-lib/react-native-host-modules.mjs +1 -1
  65. package/dist-lib/render-mode.cjs +1 -1
  66. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  67. package/dist-lib/sdk.cjs +2 -2
  68. package/dist-lib/sdk.mjs +2 -2
  69. package/dist-lib/skills.cjs +479 -204
  70. package/dist-lib/swift-compile.cjs +1 -1
  71. package/dist-lib/swift-compile.mjs +1 -1
  72. package/dist-lib/swift-package.cjs +1 -1
  73. package/dist-lib/swift-package.mjs +1 -1
  74. package/dist-lib/swift-submit.cjs +1 -1
  75. package/dist-lib/swift-submit.mjs +1 -1
  76. package/dist-lib/swift.cjs +1 -1
  77. package/dist-lib/vite-swift.cjs +21 -13
  78. package/dist-lib/vite.cjs +1 -1
  79. package/kotlin/AppSources/androidx/activity/ActivityFlags.kt +9 -0
  80. package/kotlin/AppSources/androidx/activity/BackEventCompat.kt +25 -4
  81. package/kotlin/AppSources/androidx/activity/ComponentActivity.kt +15 -4
  82. package/kotlin/AppSources/androidx/activity/EdgeToEdge.kt +12 -8
  83. package/kotlin/AppSources/androidx/activity/ExperimentalActivityApi.kt +6 -0
  84. package/kotlin/AppSources/androidx/activity/OnBackPressedCallback.kt +50 -9
  85. package/kotlin/AppSources/androidx/activity/OnBackPressedDispatcher.kt +67 -89
  86. package/kotlin/AppSources/androidx/activity/compose/BackHandler.kt +55 -24
  87. package/kotlin/AppSources/androidx/activity/compose/internal/BackHandlerCompat.kt +38 -0
  88. package/kotlin/AppSources/androidx/activity/compose/internal/BackHandlerDispatcherCompat.kt +27 -0
  89. package/kotlin/AppSources/dev/peach/app/PeachApp.kt +5 -5
  90. package/kotlin/ComposeSources/PeachComposeHost.kt +31 -7
  91. package/kotlin/fixtures/kotlin-app/app/build.gradle.kts +1 -1
  92. package/kotlin/fixtures/kotlin-app-variants/Browse.kt +73 -0
  93. package/kotlin/fixtures/kotlin-app-variants/Focus.kt +62 -0
  94. package/kotlin/fixtures/kotlin-app-variants/Lists.kt +57 -0
  95. package/kotlin/fixtures/kotlin-app-variants/Nav.kt +47 -0
  96. package/kotlin/fixtures/kotlin-app-variants/Nested.kt +64 -0
  97. package/kotlin/fixtures/kotlin-app-variants/Pan.kt +38 -0
  98. package/kotlin/fixtures/kotlin-app-variants/Pickers.kt +44 -0
  99. package/kotlin/fixtures/kotlin-app-variants/Press.kt +43 -0
  100. package/kotlin/fixtures/kotlin-app-variants/TextWidth.kt +45 -0
  101. package/kotlin/fixtures/kotlin-app-variants/Transition.kt +69 -0
  102. package/kotlin/fixtures/kotlin-app-variants/ViewConfig.kt +22 -0
  103. package/kotlin/toolchain.json +30 -2
  104. package/package.json +8 -1
  105. package/skills/contrast/SKILL.md +2 -1
  106. package/skills/peach-debug/SKILL.md +2 -2
  107. package/skills/peach-test/SKILL.md +3 -3
  108. package/src/expo-swift-host.ts +13 -4
  109. package/src/kotlin-app.ts +35 -16
  110. package/src/kotlin-host.ts +1 -0
  111. package/src/metro-plugin.ts +24 -0
  112. package/src/native-firebase-config.ts +202 -21
  113. package/src/native-firebase-metadata.ts +114 -0
  114. package/src/swift-host.ts +30 -15
  115. package/src/vite-plugin.ts +46 -2
  116. package/kotlin/AppSources/androidx/activity/Cancellable.kt +0 -5
@@ -0,0 +1,45 @@
1
+ package dev.contrast.kotlinapp
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.layout.Arrangement
5
+ import androidx.compose.foundation.layout.Column
6
+ import androidx.compose.foundation.layout.fillMaxSize
7
+ import androidx.compose.foundation.layout.padding
8
+ import androidx.compose.foundation.layout.safeDrawingPadding
9
+ import androidx.compose.material3.MaterialTheme
10
+ import androidx.compose.material3.Text
11
+ import androidx.compose.runtime.Composable
12
+ import androidx.compose.runtime.mutableStateMapOf
13
+ import androidx.compose.runtime.remember
14
+ import androidx.compose.ui.Modifier
15
+ import androidx.compose.ui.graphics.Color
16
+ import androidx.compose.ui.text.TextStyle
17
+ import androidx.compose.ui.text.font.FontWeight
18
+ import androidx.compose.ui.unit.dp
19
+ import androidx.compose.ui.unit.sp
20
+ import kotlin.math.roundToInt
21
+
22
+ // laid out widths in px of the same words at the label and body styles
23
+ @Composable
24
+ fun TextWidth() {
25
+ val widths = remember { mutableStateMapOf<String, Float>() }
26
+ val samples = listOf(
27
+ "label Month" to MaterialTheme.typography.labelLarge,
28
+ "label Compose" to MaterialTheme.typography.labelLarge,
29
+ "body Range: Day" to MaterialTheme.typography.bodyLarge,
30
+ "body Suggest" to MaterialTheme.typography.bodyLarge,
31
+ "plain500 Month" to TextStyle(fontSize = 14.sp, fontWeight = FontWeight.Medium),
32
+ "plain400 Month" to TextStyle(fontSize = 14.sp),
33
+ )
34
+ val label = MaterialTheme.typography.labelLarge
35
+ Column(
36
+ Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
37
+ verticalArrangement = Arrangement.spacedBy(12.dp),
38
+ ) {
39
+ Text("Label: ${label.fontSize} ${label.letterSpacing} ${label.fontWeight?.weight} ${label.fontFamily}")
40
+ Text("Widths: " + samples.joinToString(" ") { (key) -> "${((widths[key] ?: 0f) * 100).roundToInt() / 100.0}" })
41
+ samples.forEach { (key, style: TextStyle) ->
42
+ Text(key.substringAfter(' '), style = style, onTextLayout = { widths[key] = it.getLineRight(0) })
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,69 @@
1
+ package dev.contrast.kotlinapp
2
+
3
+ import androidx.compose.animation.AnimatedContent
4
+ import androidx.compose.animation.Crossfade
5
+ import androidx.compose.animation.animateColor
6
+ import androidx.compose.animation.core.RepeatMode
7
+ import androidx.compose.animation.core.animateDp
8
+ import androidx.compose.animation.core.animateFloat
9
+ import androidx.compose.animation.core.infiniteRepeatable
10
+ import androidx.compose.animation.core.rememberInfiniteTransition
11
+ import androidx.compose.animation.core.tween
12
+ import androidx.compose.animation.core.updateTransition
13
+ import androidx.compose.foundation.background
14
+ import androidx.compose.foundation.layout.Arrangement
15
+ import androidx.compose.foundation.layout.Box
16
+ import androidx.compose.foundation.layout.Column
17
+ import androidx.compose.foundation.layout.fillMaxSize
18
+ import androidx.compose.foundation.layout.height
19
+ import androidx.compose.foundation.layout.padding
20
+ import androidx.compose.foundation.layout.safeDrawingPadding
21
+ import androidx.compose.foundation.layout.width
22
+ import androidx.compose.material3.Button
23
+ import androidx.compose.material3.Text
24
+ import androidx.compose.runtime.Composable
25
+ import androidx.compose.runtime.LaunchedEffect
26
+ import androidx.compose.runtime.getValue
27
+ import androidx.compose.runtime.mutableIntStateOf
28
+ import androidx.compose.runtime.mutableStateOf
29
+ import androidx.compose.runtime.remember
30
+ import androidx.compose.runtime.setValue
31
+ import androidx.compose.runtime.snapshotFlow
32
+ import androidx.compose.ui.Modifier
33
+ import androidx.compose.ui.graphics.Color
34
+ import androidx.compose.ui.unit.dp
35
+
36
+ // one state drives an updateTransition's size and color, a Crossfade and,
37
+ // with a counter, an AnimatedContent. an infinite transition runs beside
38
+ // them; the status reports once its value has reached both ends, and never
39
+ // draws it, so a capture is the same at any moment
40
+ @Composable
41
+ fun Transition() {
42
+ var large by remember { mutableStateOf(false) }
43
+ var count by remember { mutableIntStateOf(0) }
44
+ val transition = updateTransition(large, label = "box")
45
+ val size by transition.animateDp(label = "size") { if (it) 160.dp else 80.dp }
46
+ val color by transition.animateColor(label = "color") { if (it) Color(0xFF6750A4) else Color(0xFFE91E63) }
47
+ val pulse = rememberInfiniteTransition(label = "pulse")
48
+ val level = pulse.animateFloat(0f, 1f, infiniteRepeatable(tween(300), RepeatMode.Reverse), label = "level")
49
+ var low by remember { mutableStateOf(false) }
50
+ var high by remember { mutableStateOf(false) }
51
+ LaunchedEffect(Unit) {
52
+ snapshotFlow { level.value }.collect {
53
+ if (it <= 0.01f) low = true
54
+ if (it >= 0.99f) high = true
55
+ }
56
+ }
57
+ val settled = !transition.isRunning && transition.currentState == transition.targetState
58
+ Column(
59
+ Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
60
+ verticalArrangement = Arrangement.spacedBy(12.dp),
61
+ ) {
62
+ Text("Transition: $large settled $settled cycled ${low && high}")
63
+ Button(onClick = { large = !large }) { Text("Switch") }
64
+ Button(onClick = { count += 1 }) { Text("Add") }
65
+ AnimatedContent(count, label = "count") { Text("Count $it") }
66
+ Crossfade(large, label = "fade") { Text(if (it) "Large" else "Small") }
67
+ Box(Modifier.width(size).height(size).background(color))
68
+ }
69
+ }
@@ -0,0 +1,22 @@
1
+ package dev.contrast.kotlinapp
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.layout.fillMaxSize
5
+ import androidx.compose.foundation.layout.padding
6
+ import androidx.compose.foundation.layout.safeDrawingPadding
7
+ import androidx.compose.material3.Text
8
+ import androidx.compose.runtime.Composable
9
+ import androidx.compose.ui.Modifier
10
+ import androidx.compose.ui.graphics.Color
11
+ import androidx.compose.ui.platform.LocalViewConfiguration
12
+ import androidx.compose.ui.unit.dp
13
+
14
+ // every value of the window's view configuration, as gestures read them
15
+ @Composable
16
+ fun ViewConfig() {
17
+ val c = LocalViewConfiguration.current
18
+ Text(
19
+ "Config: ${c.longPressTimeoutMillis} ${c.doubleTapTimeoutMillis} ${c.doubleTapMinTimeMillis} ${c.touchSlop} ${c.handwritingSlop} ${c.minimumTouchTargetSize} ${c.maximumFlingVelocity} ${c.minimumFlingVelocity} ${c.handwritingGestureLineMargin}",
20
+ Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
21
+ )
22
+ }
@@ -256,6 +256,31 @@
256
256
  "name": "savedstate-wasm-js-1.5.0.klib",
257
257
  "url": "https://dl.google.com/dl/android/maven2/androidx/savedstate/savedstate-wasm-js/1.5.0/savedstate-wasm-js-1.5.0.klib",
258
258
  "sha256": "02bb90d1173b771a9eb4ff9d2440bbe50202f0d9b0578abb2f5b47b1c27285ef"
259
+ },
260
+ {
261
+ "name": "navigation-common-wasm-js-2.10.0.klib",
262
+ "url": "https://dl.google.com/dl/android/maven2/androidx/navigation/navigation-common-wasm-js/2.10.0/navigation-common-wasm-js-2.10.0.klib",
263
+ "sha256": "ecd83faf3e808a1b90aae9b73b2c23c793b0cb8a42116cd4c0c206c4e3f3768c"
264
+ },
265
+ {
266
+ "name": "navigation-runtime-wasm-js-2.10.0.klib",
267
+ "url": "https://dl.google.com/dl/android/maven2/androidx/navigation/navigation-runtime-wasm-js/2.10.0/navigation-runtime-wasm-js-2.10.0.klib",
268
+ "sha256": "a378f0c8ea8997cdac63d4845f692abb18c8d4c3aed7acbfae32c1a10275374a"
269
+ },
270
+ {
271
+ "name": "navigation-common-wasm-js-2.10.0-beta01.klib",
272
+ "url": "https://repo1.maven.org/maven2/org/jetbrains/androidx/navigation/navigation-common-wasm-js/2.10.0-beta01/navigation-common-wasm-js-2.10.0-beta01.klib",
273
+ "sha256": "1409cfbaa2a1985886a524ff36cc73ee3ebd37ed08371194bb2fa73019d447fa"
274
+ },
275
+ {
276
+ "name": "navigation-runtime-wasm-js-2.10.0-beta01.klib",
277
+ "url": "https://repo1.maven.org/maven2/org/jetbrains/androidx/navigation/navigation-runtime-wasm-js/2.10.0-beta01/navigation-runtime-wasm-js-2.10.0-beta01.klib",
278
+ "sha256": "3682e0f481303d71f71e9713b1b1c160839567b4bea1644cc73635c141206be2"
279
+ },
280
+ {
281
+ "name": "navigation-compose-wasm-js-2.10.0-beta01.klib",
282
+ "url": "https://repo1.maven.org/maven2/org/jetbrains/androidx/navigation/navigation-compose-wasm-js/2.10.0-beta01/navigation-compose-wasm-js-2.10.0-beta01.klib",
283
+ "sha256": "35e147105bfc48ef083c56b5108436234f8d8ab4747bce855e48a30243b15cd6"
259
284
  }
260
285
  ],
261
286
  "compose": {
@@ -266,7 +291,9 @@
266
291
  "runtime": "1.13.0-alpha02",
267
292
  "material3": "1.5.0-alpha27",
268
293
  "materialRipple": "1.13.0-alpha02",
269
- "material3Ripple": "1.5.0-alpha27"
294
+ "material3Ripple": "1.5.0-alpha27",
295
+ "navigation": "2.10.0",
296
+ "activity": "1.13.0"
270
297
  },
271
298
  "mappingSources": [
272
299
  "https://repo1.maven.org/maven2/org/jetbrains/compose/ui/ui-android/1.13.0-alpha01/ui-android-1.13.0-alpha01.module",
@@ -285,7 +312,8 @@
285
312
  "https://repo1.maven.org/maven2/org/jetbrains/compose/foundation/foundation-layout-android/1.13.0-alpha01/foundation-layout-android-1.13.0-alpha01.module",
286
313
  "https://repo1.maven.org/maven2/org/jetbrains/compose/material/material-ripple-android/1.13.0-alpha01/material-ripple-android-1.13.0-alpha01.module",
287
314
  "https://repo1.maven.org/maven2/org/jetbrains/compose/animation/animation-core-android/1.13.0-alpha01/animation-core-android-1.13.0-alpha01.module",
288
- "https://repo1.maven.org/maven2/org/jetbrains/compose/material3/material3-ripple-android/1.13.0-alpha01/material3-ripple-android-1.13.0-alpha01.module"
315
+ "https://repo1.maven.org/maven2/org/jetbrains/compose/material3/material3-ripple-android/1.13.0-alpha01/material3-ripple-android-1.13.0-alpha01.module",
316
+ "https://repo1.maven.org/maven2/org/jetbrains/androidx/navigation/navigation-compose-android/2.10.0-beta01/navigation-compose-android-2.10.0-beta01.module"
289
317
  ],
290
318
  "skiko": "0.152.0-alpha02"
291
319
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peachsim",
3
- "version": "0.1.668",
3
+ "version": "0.1.670",
4
4
  "description": "Vite and Metro plugins, testing drivers, and SDK exports for peach.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -91,6 +91,13 @@
91
91
  "import": "./dist-lib/plist.mjs",
92
92
  "default": "./dist-lib/plist.cjs"
93
93
  },
94
+ "./native-firebase-metadata": {
95
+ "types": "./src/native-firebase-metadata.ts",
96
+ "source": "./src/native-firebase-metadata.ts",
97
+ "bun": "./src/native-firebase-metadata.ts",
98
+ "import": "./dist-lib/native-firebase-metadata.mjs",
99
+ "default": "./dist-lib/native-firebase-metadata.cjs"
100
+ },
94
101
  "./native-app-identity": {
95
102
  "types": "./src/native-app-identity.ts",
96
103
  "source": "./src/native-app-identity.ts",
@@ -89,6 +89,7 @@ run `peach --help` to see the full list under each group.
89
89
  - `-d, --device <model>` — override the simulated device model
90
90
  - `-t, --theme <scheme>` — override light, dark, or auto color scheme
91
91
  - `--headless` — run browser automation without a visible window where supported
92
+ - `--redact` — mask keys, tokens, account ids and emails in the output of logs, network, what-happened and get, so it can be shared
92
93
 
93
94
  ## bundled Peach agent skills
94
95
 
@@ -123,7 +124,7 @@ commands in your own `sleep` or chain `do settle` "just to be safe"**.
123
124
 
124
125
  what this means:
125
126
 
126
- - `peach do tap-id login` then `peach find "Welcome"` Just Works™.
127
+ - `peach do tap login` then `peach find "Welcome"` Just Works™.
127
128
  - you only need `peach do settle <seconds>` or `peach wait idle
128
129
  --max-ms <N>` when the CLI explicitly prints
129
130
  `⚠ auto-wait timed out after Nms — next command may see mid-animation
@@ -138,7 +138,7 @@ need a manual wait when:
138
138
  peach do settle 5 # wait up to 5s for layout stability
139
139
  peach wait idle --max-ms 5000 # same thing under the `wait` verb
140
140
  peach wait idle --strict # also require animation flags clear
141
- peach do tap-id play --no-wait # skip auto-settle on one command
141
+ peach do tap play --no-wait # skip auto-settle on one command
142
142
  PEACH_NO_AUTO_WAIT=1 peach ... # disable auto-settle for a lane
143
143
  ```
144
144
 
@@ -401,7 +401,7 @@ peach find --role button
401
401
  peach find --pressable
402
402
  peach find --visible # only on-screen nodes
403
403
 
404
- peach do tap-id loginButton # tap by testID (preferred)
404
+ peach do tap loginButton # tap by testID (preferred)
405
405
  peach do tap-text "Submit" # tap by text
406
406
  peach do tap 196 400 # tap at coordinates
407
407
  peach do type "hello world" # type into focused input
@@ -67,7 +67,7 @@ peach wait selector dashboard
67
67
  ```
68
68
 
69
69
  the batch stops on the first failure. add `--json` for per-action results.
70
- `tap <id>` and `tap-id <id>` are exact; use `tap-text <text>` for visible text.
70
+ `tap <id>` is the default way to tap by testID (`--testid <id>` and `tap-id <id>` are aliases); use `tap-text <text>` for visible text.
71
71
  punctuation in action data remains data. the exact `--then` token is reserved
72
72
  as the action boundary. use Maestro when the sequence should be reviewed,
73
73
  reused, or run in CI; an inline chain is not a third test runner and does not
@@ -89,11 +89,11 @@ Author from a live interaction:
89
89
 
90
90
  ```sh
91
91
  peach maestro start # begin a draft
92
- peach do tap-id loginButton # drive the app via the CLI
92
+ peach do tap loginButton # drive the app via the CLI
93
93
  peach maestro keep # save the step
94
94
  peach do type "user@example.com"
95
95
  peach maestro keep
96
- peach do tap-id submit
96
+ peach do tap submit
97
97
  peach maestro keep
98
98
  peach maestro end --output .maestro/login.yaml --validate
99
99
  ```
@@ -9,7 +9,13 @@ import path from 'node:path'
9
9
  import { promisify } from 'node:util'
10
10
  import { RNX_FORKED_SCRIPT_ENV } from '../cli/internal-child'
11
11
  import { isLoopbackHost } from './backend-origin'
12
- import { artifactFor, icuUrl, projectFor, serveSwiftPackageAsset } from './swift-host'
12
+ import {
13
+ artifactFor,
14
+ icuUrl,
15
+ localSwiftToolchain,
16
+ projectFor,
17
+ serveSwiftPackageAsset,
18
+ } from './swift-host'
13
19
  import { MAX_SWIFT_COMPILE_FILES, MAX_SWIFT_COMPILE_SOURCE_BYTES } from './swift-submit'
14
20
  import type { IncomingMessage, ServerResponse } from 'node:http'
15
21
 
@@ -206,6 +212,11 @@ export async function handleExpoSwiftRequest(
206
212
  pending = (async () => {
207
213
  const files = await collectExpoSwiftSources(root)
208
214
  if (!files) return null
215
+ // app Swift compiles only on this machine, never on a remote service;
216
+ // an install without the SwiftUI module and toolchain leaves the
217
+ // app's own native modules unregistered, as an unlinked module is.
218
+ const moduleRoot = path.resolve(import.meta.dirname, '../../sootsim-swift')
219
+ if (!localSwiftToolchain(moduleRoot)) return null
209
220
  const key = createHash('sha256').update(fs.realpathSync(root)).digest('hex')
210
221
  const packagePath = path.join(os.tmpdir(), 'peach-expo-swift', key)
211
222
  const sources = path.join(packagePath, 'Sources')
@@ -217,12 +228,10 @@ export async function handleExpoSwiftRequest(
217
228
  fs.mkdirSync(path.dirname(target), { recursive: true })
218
229
  fs.writeFileSync(target, content)
219
230
  }
220
- const moduleRoot = path.resolve(import.meta.dirname, '../../sootsim-swift')
221
- const local = fs.existsSync(path.join(moduleRoot, 'toolchain.json'))
222
231
  const artifact = await artifactFor(
223
232
  projectFor(packagePath),
224
233
  EXPO_SWIFT_ENTRY,
225
- local ? moduleRoot : undefined,
234
+ moduleRoot,
226
235
  )
227
236
  const host = req.headers.host
228
237
  if (!host) throw new Error('Expo Swift request has no host')
package/src/kotlin-app.ts CHANGED
@@ -44,13 +44,18 @@ export interface KotlinAppProject {
44
44
  // the activity's configChanges includes uiMode: a night mode change reaches
45
45
  // onConfigurationChanged instead of relaunching it
46
46
  handlesUiMode: boolean
47
+ // how the activity's window meets the keyboard: pan over a covered focused
48
+ // view, resize above the keyboard, or nothing
49
+ softInputAdjust: 'pan' | 'resize' | 'nothing'
47
50
  }
48
51
 
49
- export interface ComposeAndroidPins {
52
+ export interface AndroidPins {
50
53
  ui: string
51
54
  foundation: string
52
55
  runtime: string
53
56
  material3: string
57
+ navigation: string
58
+ activity: string
54
59
  }
55
60
 
56
61
  const ANDROID_NS = 'android:'
@@ -61,18 +66,17 @@ const PLUGINS = new Set([
61
66
  'org.jetbrains.kotlin.android',
62
67
  'org.jetbrains.kotlin.plugin.compose',
63
68
  ])
64
- // the closure Peach links. compose artifacts must match its Android pins; the
65
- // activity, lifecycle and savedstate artifacts are Peach's platform actuals.
66
- const PLATFORM_GROUPS = new Set([
67
- 'androidx.activity',
68
- 'androidx.lifecycle',
69
- 'androidx.savedstate',
70
- ])
71
- const COMPOSE_PIN: Record<string, keyof ComposeAndroidPins> = {
69
+ // the closure Peach links. compose, navigation and activity artifacts must
70
+ // match their Android pins (Peach's activity actuals are that version's); the
71
+ // lifecycle and savedstate artifacts are Peach's platform actuals.
72
+ const PLATFORM_GROUPS = new Set(['androidx.lifecycle', 'androidx.savedstate'])
73
+ const ANDROID_PIN: Record<string, keyof AndroidPins> = {
72
74
  'androidx.compose.ui': 'ui',
73
75
  'androidx.compose.foundation': 'foundation',
74
76
  'androidx.compose.runtime': 'runtime',
75
77
  'androidx.compose.material3': 'material3',
78
+ 'androidx.navigation': 'navigation',
79
+ 'androidx.activity': 'activity',
76
80
  }
77
81
 
78
82
  function readGradle(dir: string): { file: string; text: string } | null {
@@ -128,10 +132,7 @@ function kotlinPluginVersion(
128
132
  )
129
133
  }
130
134
 
131
- function checkModuleGradle(
132
- gradle: { file: string; text: string },
133
- pins: ComposeAndroidPins,
134
- ) {
135
+ function checkModuleGradle(gradle: { file: string; text: string }, pins: AndroidPins) {
135
136
  const at = (index: number) => `${gradle.file}:${lineOf(gradle.text, index)}`
136
137
  for (const match of gradle.text.matchAll(
137
138
  /\bid\(\s*"([^"]+)"\s*\)|\bkotlin\(\s*"([^"]+)"\s*\)/g,
@@ -166,11 +167,11 @@ function checkModuleGradle(
166
167
  if (!coordinate)
167
168
  throw new Error(`${where}: ${value} is not a literal group:artifact:version`)
168
169
  const [, group = '', artifact, version] = coordinate
169
- const pin = COMPOSE_PIN[group]
170
+ const pin = ANDROID_PIN[group]
170
171
  if (pin) {
171
172
  if (version !== pins[pin])
172
173
  throw new Error(
173
- `${where}: ${group}:${artifact}:${version} differs from Peach's Compose ${pin} ${pins[pin]}`,
174
+ `${where}: ${group}:${artifact}:${version} differs from Peach's ${pin} pin ${pins[pin]}`,
174
175
  )
175
176
  } else if (!PLATFORM_GROUPS.has(group))
176
177
  throw new Error(`${where}: ${group}:${artifact} is not in Peach's Kotlin closure`)
@@ -305,7 +306,7 @@ function isLauncher(element: XmlElement): boolean {
305
306
 
306
307
  export function readKotlinApp(
307
308
  moduleArg: string,
308
- options: { activity?: string; pins: ComposeAndroidPins },
309
+ options: { activity?: string; pins: AndroidPins },
309
310
  ): KotlinAppProject {
310
311
  const moduleDir = fs.realpathSync(path.resolve(moduleArg))
311
312
  const manifestFile = path.join(moduleDir, 'src/main/AndroidManifest.xml')
@@ -444,6 +445,23 @@ export function readKotlinApp(
444
445
  const handlesUiMode = (themeOwner?.attributes[`${ANDROID_NS}configChanges`] ?? '')
445
446
  .split('|')
446
447
  .includes('uiMode')
448
+ // the adjust part of the activity's windowSoftInputMode. ViewRootImpl
449
+ // resolves unspecified to resize only for a window holding a scroll
450
+ // container view, which a ComposeView is not, so it pans
451
+ const adjust =
452
+ (themeOwner?.attributes[`${ANDROID_NS}windowSoftInputMode`] ?? '')
453
+ .split('|')
454
+ .find((flag) => flag.startsWith('adjust')) ?? 'adjustUnspecified'
455
+ const softInputAdjust =
456
+ adjust === 'adjustUnspecified' || adjust === 'adjustPan'
457
+ ? 'pan'
458
+ : adjust === 'adjustResize'
459
+ ? 'resize'
460
+ : adjust === 'adjustNothing'
461
+ ? 'nothing'
462
+ : null
463
+ if (!softInputAdjust)
464
+ throw new Error(`${manifestFile}: unknown windowSoftInputMode ${adjust}`)
447
465
 
448
466
  const sources: string[] = []
449
467
  for (const set of ['java', 'kotlin']) {
@@ -478,5 +496,6 @@ export function readKotlinApp(
478
496
  launcher: { name: launcherName, targetClass },
479
497
  predictiveBack,
480
498
  handlesUiMode,
499
+ softInputAdjust,
481
500
  }
482
501
  }
@@ -385,6 +385,7 @@ export async function kotlinAppArtifact(
385
385
  ` { ${app.applicationClass ?? 'android.app.Application'}() },`,
386
386
  ` { ${app.launcher.targetClass}() },`,
387
387
  ` ${app.handlesUiMode},`,
388
+ ` ${kotlinString(app.softInputAdjust)},`,
388
389
  ' androidx.lifecycle.viewmodel.viewModelFactory {',
389
390
  ...viewModels.map(viewModelInitializer),
390
391
  ' },',
@@ -30,6 +30,10 @@ import {
30
30
  insertNativeAppIdentityFooter,
31
31
  } from './native-app-identity'
32
32
  import { readNativeFirebaseConfig } from './native-firebase-config'
33
+ import {
34
+ insertNativeFirebaseFooter,
35
+ readNativeFirebaseMetadata,
36
+ } from './native-firebase-metadata'
33
37
  import { parsePlistSource } from './plist'
34
38
  import {
35
39
  isRootRuntimeAssetPath,
@@ -258,6 +262,26 @@ export function withRNX<T extends Record<string, unknown>>(config: T): T {
258
262
  }
259
263
  }
260
264
  }
265
+ if (
266
+ [...(graph.dependencies?.values() ?? [])].some((module) =>
267
+ module.path.replace(/\\/g, '/').includes('/@react-native-firebase/app/'),
268
+ )
269
+ ) {
270
+ const firebase = readNativeFirebaseMetadata(
271
+ readNativeFirebaseConfig(
272
+ cfgProjectRoot,
273
+ typeof bundlePlatform === 'string' ? [bundlePlatform] : undefined,
274
+ ),
275
+ )
276
+ if (firebase)
277
+ annotated =
278
+ typeof annotated === 'string'
279
+ ? insertNativeFirebaseFooter(annotated, firebase)
280
+ : {
281
+ ...annotated,
282
+ code: insertNativeFirebaseFooter(annotated.code, firebase),
283
+ }
284
+ }
261
285
  return annotated
262
286
  },
263
287
  },