peachsim 0.1.1620 → 0.1.1635

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 (185) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +45 -6
  3. package/cli/app-config.ts +4 -1
  4. package/cli/app-fonts.ts +39 -18
  5. package/cli/app-stubs.ts +224 -9
  6. package/cli/bridge-flow-runner.ts +248 -156
  7. package/cli/cloud-client.ts +68 -57
  8. package/cli/command-registry.ts +1 -0
  9. package/cli/commands/box/search-command.ts +3 -2
  10. package/cli/commands/box.ts +10 -29
  11. package/cli/commands/control.ts +391 -92
  12. package/cli/commands/doctor.ts +17 -184
  13. package/cli/commands/flow.ts +79 -11
  14. package/cli/commands/inspect/core.ts +10 -5
  15. package/cli/commands/inspect/settling.ts +71 -22
  16. package/cli/commands/inspect/wait-ready.ts +12 -0
  17. package/cli/commands/inspect.ts +26 -3
  18. package/cli/commands/install-desktop.ts +1 -0
  19. package/cli/commands/maestro-compare.ts +105 -67
  20. package/cli/commands/maestro.ts +9 -1
  21. package/cli/commands/platform.ts +71 -36
  22. package/cli/commands/record.ts +21 -1
  23. package/cli/commands/screenshot.ts +23 -1
  24. package/cli/commands/serve.ts +7 -2
  25. package/cli/commands/setup.ts +19 -42
  26. package/cli/commands/swift.ts +5 -0
  27. package/cli/commands/upload.ts +51 -8
  28. package/cli/desktop-companion.ts +102 -17
  29. package/cli/dev-build-freshness.ts +37 -2
  30. package/cli/drivers/desktop.ts +14 -1
  31. package/cli/drivers/playwright-sim-host.ts +44 -8
  32. package/cli/drivers/playwright.ts +9 -8
  33. package/cli/drivers/types.ts +5 -0
  34. package/cli/maestro-accessibility.ts +53 -0
  35. package/cli/maestro-compare.ts +108 -12
  36. package/cli/main.ts +2 -1
  37. package/cli/outbound-endpoints.ts +35 -6
  38. package/cli/parent-pid.ts +0 -9
  39. package/cli/pause-aware-timeout.ts +102 -0
  40. package/cli/remote-simulators.ts +112 -0
  41. package/cli/remoteSimulatorContract.ts +21 -0
  42. package/cli/scene-video.ts +89 -0
  43. package/cli/ws-bridge.ts +23 -14
  44. package/detox/attempt-events.cjs +4 -0
  45. package/detox/expectations.ts +5 -1
  46. package/detox/index.ts +241 -181
  47. package/detox/motion-change.cjs +73 -0
  48. package/detox/motion-change.d.cts +21 -0
  49. package/detox/navigation-lifecycle.ts +22 -4
  50. package/dist-lib/agent-daemon-client.cjs +3 -2
  51. package/dist-lib/agent-events.cjs +1 -1
  52. package/dist-lib/attached-projects.cjs +1 -1
  53. package/dist-lib/auth/shared-session.cjs +3 -2
  54. package/dist-lib/backend-origin.cjs +1 -1
  55. package/dist-lib/beta.cjs +1 -1
  56. package/dist-lib/beta.mjs +1 -1
  57. package/dist-lib/bridge-constants.cjs +13 -1
  58. package/dist-lib/bridge-contract-input.cjs +1 -1
  59. package/dist-lib/bridge-contract-input.mjs +1 -1
  60. package/dist-lib/bridge-contract.cjs +14 -8
  61. package/dist-lib/bridge-contract.mjs +13 -7
  62. package/dist-lib/bundle-channel.cjs +10480 -7962
  63. package/dist-lib/capture-contract.cjs +1 -1
  64. package/dist-lib/capture-contract.mjs +1 -1
  65. package/dist-lib/cloud-contract.cjs +1 -1
  66. package/dist-lib/cloud-contract.mjs +1 -1
  67. package/dist-lib/cloud-test-args.cjs +1 -1
  68. package/dist-lib/cloud-test-args.mjs +1 -1
  69. package/dist-lib/cloud.cjs +3 -2
  70. package/dist-lib/cloud.mjs +3 -2
  71. package/dist-lib/config.cjs +10 -6
  72. package/dist-lib/config.mjs +10 -6
  73. package/dist-lib/detox/index.cjs +1854 -966
  74. package/dist-lib/dev-bundle-resolution.cjs +64 -24
  75. package/dist-lib/expo-swift-bundle.cjs +8715 -6271
  76. package/dist-lib/expo-swift-metadata.cjs +7 -4
  77. package/dist-lib/expo-swift-metadata.mjs +7 -4
  78. package/dist-lib/home-paths.cjs +32 -3
  79. package/dist-lib/host/bridge-host.cjs +10135 -7060
  80. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  81. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  82. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  83. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  84. package/dist-lib/host/websocket-proxy.cjs +1 -1
  85. package/dist-lib/index.cjs +1843 -408
  86. package/dist-lib/jump-to-source-babel.cjs +1 -1
  87. package/dist-lib/jump-to-source-native.cjs +1 -1
  88. package/dist-lib/kotlin-host.cjs +1 -1
  89. package/dist-lib/menu.cjs +3 -2
  90. package/dist-lib/menu.mjs +3 -2
  91. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  92. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  93. package/dist-lib/metro-production-bundle.cjs +5 -4
  94. package/dist-lib/metro-production-bundle.mjs +5 -4
  95. package/dist-lib/metro.cjs +1182 -418
  96. package/dist-lib/native-app-identity-project.cjs +782 -23
  97. package/dist-lib/native-app-identity-project.mjs +781 -23
  98. package/dist-lib/native-app-identity.cjs +276 -9
  99. package/dist-lib/native-app-identity.mjs +273 -8
  100. package/dist-lib/native-firebase-metadata.cjs +5 -4
  101. package/dist-lib/native-firebase-metadata.mjs +5 -4
  102. package/dist-lib/plist.cjs +1 -1
  103. package/dist-lib/plist.mjs +1 -1
  104. package/dist-lib/profiles.cjs +1 -1
  105. package/dist-lib/public-brand.cjs +3 -2
  106. package/dist-lib/react-native-host-modules.cjs +8 -1
  107. package/dist-lib/react-native-host-modules.mjs +8 -1
  108. package/dist-lib/scripts/dev-server-scanner.cjs +106 -28
  109. package/dist-lib/sdk.cjs +115 -34
  110. package/dist-lib/sdk.mjs +115 -33
  111. package/dist-lib/skills.cjs +15274 -9904
  112. package/dist-lib/swift-compile.cjs +1165 -173
  113. package/dist-lib/swift-compile.mjs +1212 -203
  114. package/dist-lib/swift-package.cjs +12 -3
  115. package/dist-lib/swift-package.mjs +12 -3
  116. package/dist-lib/swift-submit.cjs +7 -1
  117. package/dist-lib/swift-submit.mjs +5 -1
  118. package/dist-lib/swift-toolchain.cjs +133 -18
  119. package/dist-lib/swift.cjs +1 -1
  120. package/dist-lib/vite-swift.cjs +2648 -862
  121. package/dist-lib/vite.cjs +70 -14
  122. package/dist-lib/worklet-executable.cjs +25 -2
  123. package/dist-lib/worklet-executable.mjs +25 -2
  124. package/kotlin/AppSources/dev/peach/app/PeachDrawable.kt +21 -14
  125. package/kotlin/Sources/androidx/annotation/DrawableRes.kt +14 -0
  126. package/package.json +2 -2
  127. package/scripts/dev-server-scanner.ts +35 -14
  128. package/skills/peach-perf/SKILL.md +11 -5
  129. package/skills/peach-setup/SKILL.md +16 -19
  130. package/src/admitted-mac-build.ts +34 -8
  131. package/src/android-app-identity.ts +146 -0
  132. package/src/android-autolinking.ts +75 -0
  133. package/src/android-string-value.ts +36 -0
  134. package/src/android-strings.ts +5 -36
  135. package/src/androidAutolinkingContract.ts +40 -0
  136. package/src/bridge-constants.ts +11 -0
  137. package/src/bridge-contract.ts +22 -34
  138. package/src/bundleGlobalFooter.ts +26 -7
  139. package/src/codex-client.ts +3 -2
  140. package/src/config.ts +15 -0
  141. package/src/desktop-host-delivery.ts +5 -1
  142. package/src/dev-bundle-resolution.ts +60 -26
  143. package/src/expo-kotlin-host.ts +6 -36
  144. package/src/expo-manifest-body.ts +13 -0
  145. package/src/expo-swift-bundle.ts +17 -12
  146. package/src/expo-swift-host.ts +640 -192
  147. package/src/expo-swift-ready.ts +78 -0
  148. package/src/expoSwiftContract.ts +19 -0
  149. package/src/home-paths.ts +48 -2
  150. package/src/host/bridge-host.ts +12 -57
  151. package/src/host/staged-host-files.ts +29 -2
  152. package/src/installed-package-versions.ts +54 -14
  153. package/src/launchMetadataContract.ts +24 -0
  154. package/src/metro-plugin.ts +37 -42
  155. package/src/native-app-identity-project.ts +142 -5
  156. package/src/native-app-identity.ts +176 -11
  157. package/src/native-firebase-config.ts +209 -13
  158. package/src/nitro-compile.ts +2 -5
  159. package/src/nitro-source.ts +147 -63
  160. package/src/process-group.ts +114 -61
  161. package/src/process-output.ts +0 -9
  162. package/src/public-brand.ts +1 -0
  163. package/src/react-native-bridge-modules.ts +416 -0
  164. package/src/react-native-host-modules.ts +7 -0
  165. package/src/sim-client.ts +6 -4
  166. package/src/swift-binary-modules.ts +2 -2
  167. package/src/swift-compile.ts +1007 -108
  168. package/src/swift-compiler-digest.ts +14 -0
  169. package/src/swift-distribution.lock.json +40 -12
  170. package/src/swift-distribution.ts +61 -9
  171. package/src/swift-host.ts +149 -56
  172. package/src/swift-icu.ts +14 -6
  173. package/src/swift-objc-sdk.ts +133 -10
  174. package/src/swift-objc-standin.ts +38 -3
  175. package/src/swift-package-manifest.ts +21 -6
  176. package/src/swift-package.ts +15 -1
  177. package/src/swift-packages.ts +119 -60
  178. package/src/swift-standin-emit.ts +57 -9
  179. package/src/swift-standins.ts +185 -25
  180. package/src/swift-submit.ts +10 -0
  181. package/src/swift-toolchain.ts +149 -23
  182. package/src/vite-plugin.ts +20 -20
  183. package/src/worklet-executable.ts +30 -3
  184. package/src/worklets-babel.ts +35 -14
  185. package/src/xcode-project.ts +10 -0
@@ -3,6 +3,7 @@ import {
3
3
  isLoopbackHost,
4
4
  LOOPBACK_PROBE_HOSTNAMES,
5
5
  } from './backend-origin'
6
+ import { parseExpoManifestBody } from './expo-manifest-body'
6
7
  import {
7
8
  alignLoopbackBundleUrlWithProbe,
8
9
  METRO_BUNDLE_PATH,
@@ -159,11 +160,15 @@ type ManifestProbe =
159
160
  // manifest whose response consistently takes longer than that probe.
160
161
  export const MANIFEST_PROBE_TIMEOUT_MS = 30_000
161
162
 
162
- async function probeExpoManifest(
163
- normalizedBaseUrl: string,
164
- port: number,
165
- ): Promise<ManifestProbe> {
166
- let body: string
163
+ const manifestRequests = new Map<string, Promise<Response | undefined>>()
164
+
165
+ // the scanner, its enrichment and explicit opens share the complete response.
166
+ // a short scan deadline must not discard work that an open is still awaiting.
167
+ export async function fetchDevManifest(baseUrl: string): Promise<Response | undefined> {
168
+ const normalizedBaseUrl = baseUrl.replace(/\/+$/, '')
169
+ const existing = manifestRequests.get(normalizedBaseUrl)
170
+ if (existing) return (await existing)?.clone()
171
+
167
172
  // the deadline means STOP WAITING, not KILL THE REQUEST. `AbortSignal.timeout`
168
173
  // does the second: it tears down a dev server that is still assembling its
169
174
  // manifest, which fires "Cannot pipe to a closed or destroyed stream" into
@@ -173,39 +178,55 @@ async function probeExpoManifest(
173
178
  const pending = fetchDevProbe(`${normalizedBaseUrl}/`, {
174
179
  headers: { 'expo-platform': 'ios' },
175
180
  })
176
- .then(async (res) => ({
177
- status: res.status,
178
- contentType: res.headers.get('content-type') || '',
179
- body: await res.text(),
180
- }))
181
+ .then(async (res) => {
182
+ const body = await res.arrayBuffer()
183
+ return new Response(body.byteLength ? body : null, {
184
+ status: res.status,
185
+ headers: res.headers,
186
+ })
187
+ })
181
188
  .catch(() => undefined)
182
189
  let probeDeadline: ReturnType<typeof setTimeout> | undefined
183
- const answered = await Promise.race([
190
+ const request = Promise.race([
184
191
  pending,
185
192
  new Promise<'timeout'>((resolve) => {
186
193
  probeDeadline = setTimeout(() => resolve('timeout'), MANIFEST_PROBE_TIMEOUT_MS)
187
194
  }),
188
195
  ])
189
- // the race is settled, so drop the deadline rather than leaving it pending.
190
- clearTimeout(probeDeadline)
191
- if (answered === 'timeout')
192
- throw new Error(
193
- `Metro manifest request timed out after ${MANIFEST_PROBE_TIMEOUT_MS / 1000}s: ${normalizedBaseUrl}/. Wait for the dev server to finish starting or check its manifest endpoint.`,
194
- )
196
+ .then((answered) => {
197
+ if (answered === 'timeout')
198
+ throw new Error(
199
+ `Metro manifest request timed out after ${MANIFEST_PROBE_TIMEOUT_MS / 1000}s: ${normalizedBaseUrl}/. Wait for the dev server to finish starting or check its manifest endpoint.`,
200
+ )
201
+ return answered
202
+ })
203
+ .finally(() => {
204
+ clearTimeout(probeDeadline)
205
+ manifestRequests.delete(normalizedBaseUrl)
206
+ })
207
+ manifestRequests.set(normalizedBaseUrl, request)
208
+ return (await request)?.clone()
209
+ }
210
+
211
+ async function probeExpoManifest(
212
+ normalizedBaseUrl: string,
213
+ port: number,
214
+ ): Promise<ManifestProbe> {
215
+ const answered = await fetchDevManifest(normalizedBaseUrl)
195
216
  if (answered === undefined) return { state: 'no-answer' }
196
217
  if (answered.status === 404 || answered.status === 405) return { state: 'no-manifest' }
197
218
  if (answered.status < 200 || answered.status >= 300)
198
219
  throw new Error(
199
220
  `Metro manifest request failed: HTTP ${answered.status} at ${normalizedBaseUrl}/`,
200
221
  )
201
- body = answered.body
222
+ const body = await answered.text()
202
223
  if (!body.trim()) return { state: 'no-answer' }
203
224
 
204
225
  let manifest: unknown
205
226
  try {
206
- manifest = JSON.parse(body)
227
+ manifest = parseExpoManifestBody(body)
207
228
  } catch {
208
- if (answered.contentType.includes('json'))
229
+ if (answered.headers.get('content-type')?.includes('json'))
209
230
  throw new Error(`Metro returned invalid manifest JSON at ${normalizedBaseUrl}/`)
210
231
  // a page rather than a manifest — Metro's landing page, or an app's own
211
232
  // SSR html. that is a real answer: this server serves no manifest.
@@ -309,12 +330,25 @@ async function probeBaseUrlBundle(
309
330
  export async function probePortBundle(port: number): Promise<ResolvedDevBundle | null> {
310
331
  const viaScan = await resolvePortViaServerScan(port)
311
332
  if (viaScan) return viaScan
312
- const candidates = await Promise.all(
313
- LOOPBACK_PROBE_HOSTNAMES.map((hostname) =>
314
- probeBaseUrlBundleDirect(buildLoopbackProbeBaseUrl('http:', hostname, port), port),
315
- ),
316
- )
317
- return candidates.find((candidate) => candidate !== null) ?? null
333
+ const unresolved = buildUnresolvedTargetError(`localhost:${port}`)
334
+ try {
335
+ // a silent or failed address family must not hold back a valid sibling.
336
+ return await Promise.any(
337
+ LOOPBACK_PROBE_HOSTNAMES.map(async (hostname) => {
338
+ const bundle = await probeBaseUrlBundleDirect(
339
+ buildLoopbackProbeBaseUrl('http:', hostname, port),
340
+ port,
341
+ )
342
+ if (!bundle) throw unresolved
343
+ return bundle
344
+ }),
345
+ )
346
+ } catch (error) {
347
+ if (!(error instanceof AggregateError)) throw error
348
+ const failure = error.errors.find((candidate) => candidate !== unresolved)
349
+ if (failure !== undefined) throw failure
350
+ return null
351
+ }
318
352
  }
319
353
 
320
354
  export async function resolveConnectionInput(input: string): Promise<ResolvedDevBundle> {
@@ -1,22 +1,15 @@
1
1
  // local Expo modules on an Android device: expo's own autolinker selects the
2
2
  // app-owned Android modules, as its gradle plugin does, and their Kotlin
3
3
  // compiles into one Kotlin/Wasm process with the expo-modules-core DSL port.
4
- import { execFile } from 'node:child_process'
5
4
  import fs from 'node:fs'
6
- import { createRequire } from 'node:module'
7
5
  import path from 'node:path'
8
- import { promisify } from 'node:util'
9
- import { RNX_FORKED_SCRIPT_ENV } from '../cli/internal-child'
6
+ import { resolveExpoAndroidAutolinking } from './android-autolinking'
10
7
  import { isLoopbackHost } from './backend-origin'
11
8
  import { isRecord } from './is-record'
12
9
  import { peachKotlinModuleRoot } from './kotlin-module'
13
10
  import type { ExpoKotlinCompilationResult } from './expoKotlinContract'
14
11
  import type { IncomingMessage, ServerResponse } from 'node:http'
15
12
 
16
- const run = promisify(execFile)
17
- // the compiled CLI's execPath is peach itself, which runs a forked `.js` as a
18
- // script only with this set; node and bun ignore it.
19
- const scriptEnv = { ...process.env, [RNX_FORKED_SCRIPT_ENV]: '1' }
20
13
  export const EXPO_KOTLIN_ROUTE = '/__peach/expo-kotlin'
21
14
 
22
15
  export interface ExpoKotlinModules {
@@ -28,35 +21,12 @@ export interface ExpoKotlinModules {
28
21
  export async function collectExpoKotlinModules(
29
22
  appRoot: string,
30
23
  ): Promise<ExpoKotlinModules | null> {
31
- appRoot = fs.realpathSync(appRoot)
32
- let autolinker: string
33
- try {
34
- autolinker = createRequire(path.join(appRoot, 'package.json')).resolve(
35
- 'expo-modules-autolinking/package.json',
36
- )
37
- } catch (error) {
38
- if (isRecord(error) && error.code === 'MODULE_NOT_FOUND') return null
39
- throw error
40
- }
41
- const { stdout } = await run(
42
- process.execPath,
43
- [
44
- path.join(path.dirname(autolinker), 'bin/expo-modules-autolinking.js'),
45
- 'resolve',
46
- '--platform',
47
- 'android',
48
- '--project-root',
49
- appRoot,
50
- '--json',
51
- ],
52
- { timeout: 30_000, maxBuffer: 8 * 1024 * 1024, env: scriptEnv },
53
- )
54
- const resolved: unknown = JSON.parse(stdout)
55
- if (!isRecord(resolved) || !Array.isArray(resolved.modules))
56
- throw new Error('invalid Expo Android resolution')
24
+ const modules = await resolveExpoAndroidAutolinking(appRoot)
57
25
  const result: ExpoKotlinModules = { modules: [], sources: [] }
58
- for (const entry of resolved.modules) {
59
- if (!isRecord(entry) || !Array.isArray(entry.projects))
26
+ for (const entry of modules) {
27
+ // plugin-only dependencies have no local Kotlin module project.
28
+ if (entry.projects === undefined) continue
29
+ if (!Array.isArray(entry.projects))
60
30
  throw new Error('invalid Expo Android module entry')
61
31
  for (const project of entry.projects) {
62
32
  if (
@@ -0,0 +1,13 @@
1
+ // an Expo packager serves its manifest as JSON, or as the multipart form body
2
+ // Expo Updates defines (older CLIs always, whatever the request accepts) whose
3
+ // `manifest` part carries the same JSON.
4
+ export function parseExpoManifestBody(body: string) {
5
+ const opening = /^(--[^\r\n]+)\r?\n/.exec(body)
6
+ if (!opening) return JSON.parse(body)
7
+ for (const part of body.split(opening[1])) {
8
+ const headerEnd = part.search(/\r?\n\r?\n/)
9
+ if (headerEnd < 0 || !/\bname="manifest"/.test(part.slice(0, headerEnd))) continue
10
+ return JSON.parse(part.slice(headerEnd).trim())
11
+ }
12
+ throw new Error('Expo manifest response has no manifest part')
13
+ }
@@ -12,20 +12,25 @@ export async function buildExpoSwiftBundle(
12
12
  appRoot: string,
13
13
  ): Promise<ExpoSwiftBundle | null> {
14
14
  const assets: ExpoSwiftBundle['assets'] = []
15
- const metadata = await compileExpoSwiftModules(appRoot, '', async (artifact) => {
16
- assets.push({
17
- path: `__peach/swift/${artifact.hash}.wasm`,
18
- bytes: artifact.bytes,
19
- contentType: 'application/wasm',
20
- })
21
- if ('gzip' in artifact.icu) {
15
+ const metadata = await compileExpoSwiftModules(
16
+ appRoot,
17
+ '',
18
+ async (artifact, signal) => {
19
+ signal.throwIfAborted()
22
20
  assets.push({
23
- path: `__peach/swift-icu/${artifact.icu.sha256}`,
24
- bytes: artifact.icu.gzip,
25
- contentType: 'application/gzip',
21
+ path: `__peach/swift/${artifact.hash}.wasm`,
22
+ bytes: artifact.bytes,
23
+ contentType: 'application/wasm',
26
24
  })
27
- }
28
- })
25
+ if ('gzip' in artifact.icu) {
26
+ assets.push({
27
+ path: `__peach/swift-icu/${artifact.icu.sha256}`,
28
+ bytes: artifact.icu.gzip,
29
+ contentType: 'application/gzip',
30
+ })
31
+ }
32
+ },
33
+ )
29
34
  if (!metadata) return null
30
35
 
31
36
  return { metadata, assets }