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.
- package/README.md +35 -5
- package/cli/app-fonts.ts +2 -1
- package/cli/bridge-flow-runner.ts +184 -113
- package/cli/cloud-client.ts +68 -57
- package/cli/commands/box/search-command.ts +3 -2
- package/cli/commands/box.ts +10 -29
- package/cli/commands/control.ts +108 -43
- package/cli/commands/inspect/settling.ts +57 -20
- package/cli/commands/maestro-compare.ts +11 -32
- package/cli/commands/platform.ts +71 -36
- package/cli/commands/upload.ts +5 -1
- package/cli/dev-build-freshness.ts +37 -2
- package/cli/drivers/playwright-sim-host.ts +6 -5
- package/cli/maestro-accessibility.ts +53 -0
- package/cli/maestro-compare.ts +24 -0
- package/cli/outbound-endpoints.ts +22 -5
- package/cli/parent-pid.ts +0 -9
- package/cli/remote-simulators.ts +112 -0
- package/cli/remoteSimulatorContract.ts +21 -0
- package/detox/expectations.ts +5 -1
- package/detox/index.ts +133 -98
- package/detox/motion-change.cjs +37 -0
- package/detox/motion-change.d.cts +16 -0
- package/detox/navigation-lifecycle.ts +22 -4
- package/dist-lib/agent-daemon-client.cjs +3 -2
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +3 -2
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/bundle-channel.cjs +1520 -934
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +3 -2
- package/dist-lib/cloud.mjs +3 -2
- package/dist-lib/config.cjs +5 -4
- package/dist-lib/config.mjs +5 -4
- package/dist-lib/detox/index.cjs +1021 -364
- package/dist-lib/dev-bundle-resolution.cjs +51 -23
- package/dist-lib/expo-swift-bundle.cjs +1343 -801
- package/dist-lib/expo-swift-metadata.cjs +5 -4
- package/dist-lib/expo-swift-metadata.mjs +5 -4
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +2468 -1341
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +1262 -300
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/kotlin-host.cjs +1 -1
- package/dist-lib/menu.cjs +3 -2
- package/dist-lib/menu.mjs +3 -2
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +5 -4
- package/dist-lib/metro-production-bundle.mjs +5 -4
- package/dist-lib/metro.cjs +799 -398
- package/dist-lib/native-app-identity-project.cjs +728 -22
- package/dist-lib/native-app-identity-project.mjs +727 -22
- package/dist-lib/native-app-identity.cjs +224 -9
- package/dist-lib/native-app-identity.mjs +222 -8
- package/dist-lib/native-firebase-metadata.cjs +5 -4
- package/dist-lib/native-firebase-metadata.mjs +5 -4
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +3 -2
- package/dist-lib/react-native-host-modules.cjs +4 -1
- package/dist-lib/react-native-host-modules.mjs +4 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +94 -28
- package/dist-lib/sdk.cjs +89 -24
- package/dist-lib/sdk.mjs +89 -24
- package/dist-lib/skills.cjs +4391 -2350
- package/dist-lib/swift-compile.cjs +328 -142
- package/dist-lib/swift-compile.mjs +342 -147
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift-toolchain.cjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +888 -559
- package/dist-lib/vite.cjs +68 -14
- package/dist-lib/worklet-executable.cjs +25 -2
- package/dist-lib/worklet-executable.mjs +25 -2
- package/package.json +2 -2
- package/scripts/dev-server-scanner.ts +33 -13
- package/src/admitted-mac-build.ts +34 -8
- package/src/android-app-identity.ts +146 -0
- package/src/android-autolinking.ts +75 -0
- package/src/android-string-value.ts +36 -0
- package/src/android-strings.ts +5 -36
- package/src/androidAutolinkingContract.ts +40 -0
- package/src/bundleGlobalFooter.ts +26 -7
- package/src/codex-client.ts +3 -2
- package/src/dev-bundle-resolution.ts +58 -25
- package/src/expo-kotlin-host.ts +6 -36
- package/src/expo-swift-bundle.ts +17 -12
- package/src/expo-swift-host.ts +477 -173
- package/src/expoSwiftContract.ts +6 -0
- package/src/host/bridge-host.ts +7 -55
- package/src/host/staged-host-files.ts +29 -2
- package/src/installed-package-versions.ts +54 -14
- package/src/launchMetadataContract.ts +24 -0
- package/src/metro-plugin.ts +37 -42
- package/src/native-app-identity-project.ts +116 -3
- package/src/native-app-identity.ts +86 -11
- package/src/nitro-compile.ts +2 -5
- package/src/nitro-source.ts +147 -63
- package/src/process-group.ts +90 -60
- package/src/process-output.ts +0 -9
- package/src/public-brand.ts +1 -0
- package/src/react-native-host-modules.ts +3 -0
- package/src/sim-client.ts +6 -4
- package/src/swift-compile.ts +176 -28
- package/src/swift-distribution.lock.json +37 -11
- package/src/swift-distribution.ts +61 -9
- package/src/swift-host.ts +58 -27
- package/src/swift-objc-standin.ts +13 -0
- package/src/swift-standin-emit.ts +1 -1
- package/src/swift-standins.ts +17 -5
- package/src/vite-plugin.ts +8 -19
- package/src/worklet-executable.ts +30 -3
- package/src/worklets-babel.ts +35 -14
- package/src/xcode-project.ts +10 -0
package/src/android-strings.ts
CHANGED
|
@@ -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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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,
|
package/src/codex-client.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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.
|
|
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.
|
|
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
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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> {
|
package/src/expo-kotlin-host.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
59
|
-
|
|
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 (
|
package/src/expo-swift-bundle.ts
CHANGED
|
@@ -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(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
24
|
-
bytes: artifact.
|
|
25
|
-
contentType: 'application/
|
|
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 }
|