peachsim 0.1.1620 → 0.1.1625

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 (138) hide show
  1. package/README.md +35 -5
  2. package/cli/app-fonts.ts +2 -1
  3. package/cli/bridge-flow-runner.ts +184 -113
  4. package/cli/cloud-client.ts +68 -57
  5. package/cli/commands/box/search-command.ts +3 -2
  6. package/cli/commands/box.ts +10 -29
  7. package/cli/commands/control.ts +108 -43
  8. package/cli/commands/inspect/settling.ts +57 -20
  9. package/cli/commands/maestro-compare.ts +11 -32
  10. package/cli/commands/platform.ts +71 -36
  11. package/cli/commands/upload.ts +5 -1
  12. package/cli/dev-build-freshness.ts +37 -2
  13. package/cli/drivers/playwright-sim-host.ts +6 -5
  14. package/cli/maestro-accessibility.ts +53 -0
  15. package/cli/maestro-compare.ts +24 -0
  16. package/cli/outbound-endpoints.ts +22 -5
  17. package/cli/parent-pid.ts +0 -9
  18. package/cli/remote-simulators.ts +112 -0
  19. package/cli/remoteSimulatorContract.ts +21 -0
  20. package/detox/expectations.ts +5 -1
  21. package/detox/index.ts +133 -98
  22. package/detox/motion-change.cjs +37 -0
  23. package/detox/motion-change.d.cts +16 -0
  24. package/detox/navigation-lifecycle.ts +22 -4
  25. package/dist-lib/agent-daemon-client.cjs +3 -2
  26. package/dist-lib/agent-events.cjs +1 -1
  27. package/dist-lib/attached-projects.cjs +1 -1
  28. package/dist-lib/auth/shared-session.cjs +3 -2
  29. package/dist-lib/backend-origin.cjs +1 -1
  30. package/dist-lib/beta.cjs +1 -1
  31. package/dist-lib/beta.mjs +1 -1
  32. package/dist-lib/bridge-constants.cjs +1 -1
  33. package/dist-lib/bridge-contract-input.cjs +1 -1
  34. package/dist-lib/bridge-contract-input.mjs +1 -1
  35. package/dist-lib/bridge-contract.cjs +1 -1
  36. package/dist-lib/bridge-contract.mjs +1 -1
  37. package/dist-lib/bundle-channel.cjs +1520 -934
  38. package/dist-lib/capture-contract.cjs +1 -1
  39. package/dist-lib/capture-contract.mjs +1 -1
  40. package/dist-lib/cloud-contract.cjs +1 -1
  41. package/dist-lib/cloud-contract.mjs +1 -1
  42. package/dist-lib/cloud-test-args.cjs +1 -1
  43. package/dist-lib/cloud-test-args.mjs +1 -1
  44. package/dist-lib/cloud.cjs +3 -2
  45. package/dist-lib/cloud.mjs +3 -2
  46. package/dist-lib/config.cjs +5 -4
  47. package/dist-lib/config.mjs +5 -4
  48. package/dist-lib/detox/index.cjs +1021 -364
  49. package/dist-lib/dev-bundle-resolution.cjs +51 -23
  50. package/dist-lib/expo-swift-bundle.cjs +1343 -801
  51. package/dist-lib/expo-swift-metadata.cjs +5 -4
  52. package/dist-lib/expo-swift-metadata.mjs +5 -4
  53. package/dist-lib/home-paths.cjs +1 -1
  54. package/dist-lib/host/bridge-host.cjs +2468 -1341
  55. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  56. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  57. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  58. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  59. package/dist-lib/host/websocket-proxy.cjs +1 -1
  60. package/dist-lib/index.cjs +1262 -300
  61. package/dist-lib/jump-to-source-babel.cjs +1 -1
  62. package/dist-lib/jump-to-source-native.cjs +1 -1
  63. package/dist-lib/kotlin-host.cjs +1 -1
  64. package/dist-lib/menu.cjs +3 -2
  65. package/dist-lib/menu.mjs +3 -2
  66. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  67. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  68. package/dist-lib/metro-production-bundle.cjs +5 -4
  69. package/dist-lib/metro-production-bundle.mjs +5 -4
  70. package/dist-lib/metro.cjs +799 -398
  71. package/dist-lib/native-app-identity-project.cjs +728 -22
  72. package/dist-lib/native-app-identity-project.mjs +727 -22
  73. package/dist-lib/native-app-identity.cjs +224 -9
  74. package/dist-lib/native-app-identity.mjs +222 -8
  75. package/dist-lib/native-firebase-metadata.cjs +5 -4
  76. package/dist-lib/native-firebase-metadata.mjs +5 -4
  77. package/dist-lib/plist.cjs +1 -1
  78. package/dist-lib/plist.mjs +1 -1
  79. package/dist-lib/profiles.cjs +1 -1
  80. package/dist-lib/public-brand.cjs +3 -2
  81. package/dist-lib/react-native-host-modules.cjs +4 -1
  82. package/dist-lib/react-native-host-modules.mjs +4 -1
  83. package/dist-lib/scripts/dev-server-scanner.cjs +94 -28
  84. package/dist-lib/sdk.cjs +89 -24
  85. package/dist-lib/sdk.mjs +89 -24
  86. package/dist-lib/skills.cjs +4391 -2350
  87. package/dist-lib/swift-compile.cjs +328 -142
  88. package/dist-lib/swift-compile.mjs +342 -147
  89. package/dist-lib/swift-package.cjs +1 -1
  90. package/dist-lib/swift-package.mjs +1 -1
  91. package/dist-lib/swift-submit.cjs +1 -1
  92. package/dist-lib/swift-submit.mjs +1 -1
  93. package/dist-lib/swift-toolchain.cjs +1 -1
  94. package/dist-lib/swift.cjs +1 -1
  95. package/dist-lib/vite-swift.cjs +888 -559
  96. package/dist-lib/vite.cjs +68 -14
  97. package/dist-lib/worklet-executable.cjs +25 -2
  98. package/dist-lib/worklet-executable.mjs +25 -2
  99. package/package.json +2 -2
  100. package/scripts/dev-server-scanner.ts +33 -13
  101. package/src/admitted-mac-build.ts +34 -8
  102. package/src/android-app-identity.ts +146 -0
  103. package/src/android-autolinking.ts +75 -0
  104. package/src/android-string-value.ts +36 -0
  105. package/src/android-strings.ts +5 -36
  106. package/src/androidAutolinkingContract.ts +40 -0
  107. package/src/bundleGlobalFooter.ts +26 -7
  108. package/src/codex-client.ts +3 -2
  109. package/src/dev-bundle-resolution.ts +58 -25
  110. package/src/expo-kotlin-host.ts +6 -36
  111. package/src/expo-swift-bundle.ts +17 -12
  112. package/src/expo-swift-host.ts +477 -173
  113. package/src/expoSwiftContract.ts +6 -0
  114. package/src/host/bridge-host.ts +7 -55
  115. package/src/host/staged-host-files.ts +29 -2
  116. package/src/installed-package-versions.ts +54 -14
  117. package/src/launchMetadataContract.ts +24 -0
  118. package/src/metro-plugin.ts +37 -42
  119. package/src/native-app-identity-project.ts +116 -3
  120. package/src/native-app-identity.ts +86 -11
  121. package/src/nitro-compile.ts +2 -5
  122. package/src/nitro-source.ts +147 -63
  123. package/src/process-group.ts +90 -60
  124. package/src/process-output.ts +0 -9
  125. package/src/public-brand.ts +1 -0
  126. package/src/react-native-host-modules.ts +3 -0
  127. package/src/sim-client.ts +6 -4
  128. package/src/swift-compile.ts +176 -28
  129. package/src/swift-distribution.lock.json +37 -11
  130. package/src/swift-distribution.ts +61 -9
  131. package/src/swift-host.ts +58 -27
  132. package/src/swift-objc-standin.ts +13 -0
  133. package/src/swift-standin-emit.ts +1 -1
  134. package/src/swift-standins.ts +17 -5
  135. package/src/vite-plugin.ts +8 -19
  136. package/src/worklet-executable.ts +30 -3
  137. package/src/worklets-babel.ts +35 -14
  138. package/src/xcode-project.ts +10 -0
@@ -6,6 +6,7 @@
6
6
  // rejected by name, never read as the default or as text.
7
7
  import fs from 'node:fs'
8
8
  import path from 'node:path'
9
+ import { compileAndroidStringValue } from './android-string-value'
9
10
  import { decodeXmlText } from './android-xml'
10
11
 
11
12
  // one resource's value per locale: '' for values, then a language or a
@@ -123,41 +124,9 @@ const sorted = <Value>(
123
124
  .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
124
125
  .map(([name, values]) => ({ name, values }))
125
126
 
126
- // aapt2's StringBuilder over the decoded text: outside double quotes a run of
127
- // whitespace is one space and the ends are trimmed, an unescaped apostrophe is
128
- // an error; a double quote toggles quoting and is dropped; a backslash escapes
129
- // the next character (\n, \t and \uXXXX, any other character as itself). a
130
- // value beginning with @ or ? is a reference aapt2 resolves, which Peach does
131
- // not yet
132
127
  function compileString(raw: string, where: string): string {
133
- const text = decodeXmlText(raw.replace(/<[^>]*>/g, ''), where)
134
- if (/^\s*[@?]/.test(text))
135
- throw new Error(`${where} is a resource reference, not resolved by peach kotlin yet`)
136
- const out: { char: string; soft: boolean }[] = []
137
- let quoted = false
138
- for (let index = 0; index < text.length; index++) {
139
- const char = text[index] ?? ''
140
- if (char === '\\') {
141
- const next = text[index + 1]
142
- if (next === undefined) break
143
- index++
144
- if (next === 'n') out.push({ char: '\n', soft: false })
145
- else if (next === 't') out.push({ char: '\t', soft: false })
146
- else if (next === 'u') {
147
- const hex = text.slice(index + 1, index + 5)
148
- if (!/^[0-9a-fA-F]{4}$/.test(hex))
149
- throw new Error(`${where} has an invalid unicode escape \\u${hex}`)
150
- out.push({ char: String.fromCharCode(Number.parseInt(hex, 16)), soft: false })
151
- index += 4
152
- } else out.push({ char: next, soft: false })
153
- } else if (char === '"') quoted = !quoted
154
- else if (char === "'" && !quoted)
155
- throw new Error(`${where} has an unescaped apostrophe`)
156
- else if (!quoted && /\s/.test(char)) {
157
- if (out.at(-1)?.soft !== true) out.push({ char: ' ', soft: true })
158
- } else out.push({ char, soft: false })
159
- }
160
- while (out[0]?.soft) out.shift()
161
- while (out.at(-1)?.soft) out.pop()
162
- return out.map((part) => part.char).join('')
128
+ return compileAndroidStringValue(
129
+ decodeXmlText(raw.replace(/<[^>]*>/g, ''), where),
130
+ where,
131
+ )
163
132
  }
@@ -0,0 +1,40 @@
1
+ import {
2
+ detachBundleGlobalFooter,
3
+ insertBundleGlobalFooter,
4
+ readBundleGlobalFooter,
5
+ } from './bundleGlobalFooter'
6
+
7
+ const FOOTER = '__peachAndroidAutolinkedPackages'
8
+
9
+ export function readAndroidAutolinkedPackages(value: unknown): string[] {
10
+ if (
11
+ !Array.isArray(value) ||
12
+ !value.every((name) => typeof name === 'string' && name.length > 0)
13
+ )
14
+ throw new Error('invalid Android autolinked packages')
15
+ return [...new Set(value)].sort((left, right) => left.localeCompare(right))
16
+ }
17
+
18
+ export function readAndroidAutolinkingFooter(source: string): string[] | null {
19
+ const value = readBundleGlobalFooter(source, FOOTER)
20
+ return value === null ? null : readAndroidAutolinkedPackages(value)
21
+ }
22
+
23
+ export function insertAndroidAutolinkingFooter(
24
+ source: string,
25
+ packages: readonly string[],
26
+ ): string {
27
+ return insertBundleGlobalFooter(source, FOOTER, readAndroidAutolinkedPackages(packages))
28
+ }
29
+
30
+ export function detachAndroidAutolinkingFooter(source: string): {
31
+ source: string
32
+ packages: string[] | null
33
+ } {
34
+ const detached = detachBundleGlobalFooter(source, FOOTER)
35
+ return {
36
+ source: detached.source,
37
+ packages:
38
+ detached.value === null ? null : readAndroidAutolinkedPackages(detached.value),
39
+ }
40
+ }
@@ -14,18 +14,37 @@ export function findBundleFooter(source: string, prefix: string): number {
14
14
  if (newline < 0) return -1
15
15
  const line = newline + 1
16
16
  if (source.startsWith(prefix, line)) return newline
17
- if (
18
- line !== end &&
19
- !source.startsWith('globalThis.', line) &&
20
- !source.startsWith('//', line)
21
- ) {
22
- return -1
23
- }
17
+ if (!isFooterBlockLine(source, line, end)) return -1
24
18
  end = newline
25
19
  }
26
20
  return -1
27
21
  }
28
22
 
23
+ function isFooterBlockLine(source: string, line: number, end: number): boolean {
24
+ return (
25
+ line === end ||
26
+ source.startsWith('globalThis.', line) ||
27
+ source.startsWith('//', line)
28
+ )
29
+ }
30
+
31
+ // the whole trailing block the readers above walk, split from the code before
32
+ // it. a rewrite of the code (minification) must leave the block as written:
33
+ // a minifier reprints each footer's JSON as an object literal and moves legal
34
+ // comments after it, so no reader would find the footer again.
35
+ export function splitBundleFooterBlock(source: string): {
36
+ code: string
37
+ footers: string
38
+ } {
39
+ let end = source.length
40
+ while (end > 0) {
41
+ const newline = source.lastIndexOf('\n', end - 1)
42
+ if (newline < 0 || !isFooterBlockLine(source, newline + 1, end)) break
43
+ end = newline
44
+ }
45
+ return { code: source.slice(0, end), footers: source.slice(end) }
46
+ }
47
+
29
48
  function readFooter(
30
49
  source: string,
31
50
  prefix: string,
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'
11
11
  import readline from 'node:readline'
12
+ import { terminateProcessGroup } from './process-group'
12
13
 
13
14
  export interface CodexClientOptions {
14
15
  /** resolved path to the codex binary (via `which codex` or user override). */
@@ -211,10 +212,10 @@ export function spawnCodexClient(options: CodexClientOptions): CodexClient {
211
212
  try {
212
213
  child.stdin.end()
213
214
  } catch {}
214
- const t = setTimeout(() => {
215
+ const t = setTimeout(async () => {
215
216
  if (!closed) {
216
217
  try {
217
- child.kill('SIGTERM')
218
+ await terminateProcessGroup(child, 'SIGTERM')
218
219
  } catch {}
219
220
  }
220
221
  }, timeoutMs)
@@ -159,11 +159,15 @@ type ManifestProbe =
159
159
  // manifest whose response consistently takes longer than that probe.
160
160
  export const MANIFEST_PROBE_TIMEOUT_MS = 30_000
161
161
 
162
- async function probeExpoManifest(
163
- normalizedBaseUrl: string,
164
- port: number,
165
- ): Promise<ManifestProbe> {
166
- let body: string
162
+ const manifestRequests = new Map<string, Promise<Response | undefined>>()
163
+
164
+ // the scanner, its enrichment and explicit opens share the complete response.
165
+ // a short scan deadline must not discard work that an open is still awaiting.
166
+ export async function fetchDevManifest(baseUrl: string): Promise<Response | undefined> {
167
+ const normalizedBaseUrl = baseUrl.replace(/\/+$/, '')
168
+ const existing = manifestRequests.get(normalizedBaseUrl)
169
+ if (existing) return (await existing)?.clone()
170
+
167
171
  // the deadline means STOP WAITING, not KILL THE REQUEST. `AbortSignal.timeout`
168
172
  // does the second: it tears down a dev server that is still assembling its
169
173
  // manifest, which fires "Cannot pipe to a closed or destroyed stream" into
@@ -173,39 +177,55 @@ async function probeExpoManifest(
173
177
  const pending = fetchDevProbe(`${normalizedBaseUrl}/`, {
174
178
  headers: { 'expo-platform': 'ios' },
175
179
  })
176
- .then(async (res) => ({
177
- status: res.status,
178
- contentType: res.headers.get('content-type') || '',
179
- body: await res.text(),
180
- }))
180
+ .then(async (res) => {
181
+ const body = await res.arrayBuffer()
182
+ return new Response(body.byteLength ? body : null, {
183
+ status: res.status,
184
+ headers: res.headers,
185
+ })
186
+ })
181
187
  .catch(() => undefined)
182
188
  let probeDeadline: ReturnType<typeof setTimeout> | undefined
183
- const answered = await Promise.race([
189
+ const request = Promise.race([
184
190
  pending,
185
191
  new Promise<'timeout'>((resolve) => {
186
192
  probeDeadline = setTimeout(() => resolve('timeout'), MANIFEST_PROBE_TIMEOUT_MS)
187
193
  }),
188
194
  ])
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
- )
195
+ .then((answered) => {
196
+ if (answered === 'timeout')
197
+ throw new Error(
198
+ `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.`,
199
+ )
200
+ return answered
201
+ })
202
+ .finally(() => {
203
+ clearTimeout(probeDeadline)
204
+ manifestRequests.delete(normalizedBaseUrl)
205
+ })
206
+ manifestRequests.set(normalizedBaseUrl, request)
207
+ return (await request)?.clone()
208
+ }
209
+
210
+ async function probeExpoManifest(
211
+ normalizedBaseUrl: string,
212
+ port: number,
213
+ ): Promise<ManifestProbe> {
214
+ const answered = await fetchDevManifest(normalizedBaseUrl)
195
215
  if (answered === undefined) return { state: 'no-answer' }
196
216
  if (answered.status === 404 || answered.status === 405) return { state: 'no-manifest' }
197
217
  if (answered.status < 200 || answered.status >= 300)
198
218
  throw new Error(
199
219
  `Metro manifest request failed: HTTP ${answered.status} at ${normalizedBaseUrl}/`,
200
220
  )
201
- body = answered.body
221
+ const body = await answered.text()
202
222
  if (!body.trim()) return { state: 'no-answer' }
203
223
 
204
224
  let manifest: unknown
205
225
  try {
206
226
  manifest = JSON.parse(body)
207
227
  } catch {
208
- if (answered.contentType.includes('json'))
228
+ if (answered.headers.get('content-type')?.includes('json'))
209
229
  throw new Error(`Metro returned invalid manifest JSON at ${normalizedBaseUrl}/`)
210
230
  // a page rather than a manifest — Metro's landing page, or an app's own
211
231
  // SSR html. that is a real answer: this server serves no manifest.
@@ -309,12 +329,25 @@ async function probeBaseUrlBundle(
309
329
  export async function probePortBundle(port: number): Promise<ResolvedDevBundle | null> {
310
330
  const viaScan = await resolvePortViaServerScan(port)
311
331
  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
332
+ const unresolved = buildUnresolvedTargetError(`localhost:${port}`)
333
+ try {
334
+ // a silent or failed address family must not hold back a valid sibling.
335
+ return await Promise.any(
336
+ LOOPBACK_PROBE_HOSTNAMES.map(async (hostname) => {
337
+ const bundle = await probeBaseUrlBundleDirect(
338
+ buildLoopbackProbeBaseUrl('http:', hostname, port),
339
+ port,
340
+ )
341
+ if (!bundle) throw unresolved
342
+ return bundle
343
+ }),
344
+ )
345
+ } catch (error) {
346
+ if (!(error instanceof AggregateError)) throw error
347
+ const failure = error.errors.find((candidate) => candidate !== unresolved)
348
+ if (failure !== undefined) throw failure
349
+ return null
350
+ }
318
351
  }
319
352
 
320
353
  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 (
@@ -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 }