peachsim 0.1.638 → 0.1.639

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 (80) hide show
  1. package/cli/command-registry.ts +1 -0
  2. package/cli/commands/control.ts +42 -5
  3. package/cli/commands/swift.ts +326 -0
  4. package/cli/main.ts +11 -0
  5. package/cli/outbound-endpoints.ts +5 -1
  6. package/dist-lib/agent-daemon-client.cjs +1 -1
  7. package/dist-lib/agent-events.cjs +1 -1
  8. package/dist-lib/agent-identity.cjs +1 -1
  9. package/dist-lib/agent-sessions.cjs +1 -1
  10. package/dist-lib/attached-projects.cjs +1 -1
  11. package/dist-lib/auth/shared-session.cjs +1 -1
  12. package/dist-lib/backend-origin.cjs +1 -1
  13. package/dist-lib/beta.cjs +1 -1
  14. package/dist-lib/beta.mjs +1 -1
  15. package/dist-lib/bridge-constants.cjs +1 -1
  16. package/dist-lib/bridge-contract-input.cjs +1 -1
  17. package/dist-lib/bridge-contract-input.mjs +1 -1
  18. package/dist-lib/bridge-contract.cjs +1 -1
  19. package/dist-lib/bridge-contract.mjs +1 -1
  20. package/dist-lib/capture-contract.cjs +1 -1
  21. package/dist-lib/capture-contract.mjs +1 -1
  22. package/dist-lib/cli-constants.cjs +1 -1
  23. package/dist-lib/cloud-contract.cjs +1 -1
  24. package/dist-lib/cloud-contract.mjs +1 -1
  25. package/dist-lib/cloud-test-args.cjs +1 -1
  26. package/dist-lib/cloud-test-args.mjs +1 -1
  27. package/dist-lib/cloud.cjs +1 -1
  28. package/dist-lib/cloud.mjs +1 -1
  29. package/dist-lib/config.cjs +1 -1
  30. package/dist-lib/detox/index.cjs +1 -1
  31. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  32. package/dist-lib/home-paths.cjs +1 -1
  33. package/dist-lib/host/bridge-host.cjs +1 -1
  34. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  35. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  36. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  37. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  38. package/dist-lib/host/websocket-proxy.cjs +1 -1
  39. package/dist-lib/index.cjs +13 -4
  40. package/dist-lib/jump-to-source-babel.cjs +1 -1
  41. package/dist-lib/jump-to-source-native.cjs +1 -1
  42. package/dist-lib/kotlin-host.cjs +1 -1
  43. package/dist-lib/menu.cjs +1 -1
  44. package/dist-lib/menu.mjs +1 -1
  45. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  46. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  47. package/dist-lib/metro-production-bundle.cjs +1 -1
  48. package/dist-lib/metro-production-bundle.mjs +1 -1
  49. package/dist-lib/metro.cjs +1 -1
  50. package/dist-lib/native-app-identity.cjs +1 -1
  51. package/dist-lib/native-app-identity.mjs +1 -1
  52. package/dist-lib/profiles.cjs +1 -1
  53. package/dist-lib/public-brand.cjs +1 -1
  54. package/dist-lib/react-native-host-modules.cjs +1 -1
  55. package/dist-lib/react-native-host-modules.mjs +1 -1
  56. package/dist-lib/render-mode.cjs +1 -1
  57. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  58. package/dist-lib/sdk.cjs +1 -1
  59. package/dist-lib/sdk.mjs +1 -1
  60. package/dist-lib/skills.cjs +48 -33
  61. package/dist-lib/swift-compile.cjs +1 -1
  62. package/dist-lib/swift-compile.mjs +1 -1
  63. package/dist-lib/swift-package.cjs +1 -1
  64. package/dist-lib/swift-package.mjs +1 -1
  65. package/dist-lib/swift-submit.cjs +89 -8
  66. package/dist-lib/swift-submit.mjs +78 -8
  67. package/dist-lib/swift.cjs +16 -33308
  68. package/dist-lib/vite-swift.cjs +422 -247
  69. package/dist-lib/vite.cjs +13 -4
  70. package/package.json +1 -1
  71. package/skills/contrast/SKILL.md +1 -0
  72. package/src/assetCatalogContract.ts +16 -0
  73. package/src/metro-runtime-source.ts +15 -0
  74. package/src/swift-guest.ts +51 -0
  75. package/src/swift-host.ts +900 -0
  76. package/src/swift-submit.ts +97 -6
  77. package/src/swift.ts +36 -8
  78. package/src/swiftContract.ts +52 -0
  79. package/src/vite-plugin-one.ts +4 -3
  80. package/src/vite-plugin-swift.ts +43 -755
@@ -42,6 +42,7 @@ const NODE_COMMAND_NAMES = [
42
42
  'android',
43
43
  'remote',
44
44
  'preview',
45
+ 'swift',
45
46
  'box',
46
47
  'list',
47
48
  'use',
@@ -99,6 +99,14 @@ interface ControlCommandOptions {
99
99
  runtimeConfig?: RNXConfig
100
100
  }
101
101
 
102
+ export interface OpenCommandResources {
103
+ bridge?: ReturnType<typeof spawn>
104
+ bridgeReady?: boolean
105
+ port?: number
106
+ driver?: DriverLaunchResult & { driverId: DriverId }
107
+ simId?: string
108
+ }
109
+
102
110
  interface DriverConnectWaitOptions {
103
111
  attempts: number
104
112
  intervalMs: number
@@ -184,6 +192,7 @@ function normalizeKnownTarget(target: string): string {
184
192
  export async function ensureBridgeForOpen(
185
193
  preferredPort: number,
186
194
  explicitPort = false,
195
+ resources?: OpenCommandResources,
187
196
  ): Promise<number> {
188
197
  const lock = readDaemonLockfile()
189
198
  assertExplicitBridgeLock(preferredPort, explicitPort, lock)
@@ -218,6 +227,10 @@ export async function ensureBridgeForOpen(
218
227
 
219
228
  await ensureRuntimeForOpen()
220
229
  const child = spawnSootsim(['serve', '--quiet', '--port', String(preferredPort)])
230
+ if (resources) {
231
+ resources.bridge = child
232
+ resources.port = preferredPort
233
+ }
221
234
  let childExit: string | null = null
222
235
  child.once('exit', (code, signal) => {
223
236
  childExit = String(code ?? signal ?? 'unknown')
@@ -252,6 +265,10 @@ export async function ensureBridgeForOpen(
252
265
  console.error(
253
266
  ` Started a local bridge on port ${selectedPort}. Run \`peach daemon install\` to keep it ready between commands.`,
254
267
  )
268
+ if (resources) {
269
+ resources.bridgeReady = true
270
+ resources.port = selectedPort
271
+ }
255
272
  return selectedPort
256
273
  }
257
274
  if (childExit) {
@@ -1048,13 +1065,14 @@ export async function waitForSimMatchOrHostExit(
1048
1065
  commandTimeoutMs: number,
1049
1066
  predicate: (sim: BridgeSimInfo) => boolean,
1050
1067
  hostPid: number | undefined,
1051
- opts: { attempts?: number; intervalMs?: number } = {},
1068
+ opts: { attempts?: number; intervalMs?: number; signal?: AbortSignal } = {},
1052
1069
  ): Promise<WaitForSimMatchOrHostExitResult> {
1053
1070
  const attempts = opts.attempts ?? 30
1054
1071
  const intervalMs = opts.intervalMs ?? 500
1055
1072
  const watchHost = !!hostPid && Number.isInteger(hostPid) && hostPid > 1
1056
1073
  const hostExited = () => watchHost && hostPid !== undefined && !isProcessAlive(hostPid)
1057
1074
  for (let i = 0; i < attempts; i++) {
1075
+ opts.signal?.throwIfAborted()
1058
1076
  if (hostExited()) {
1059
1077
  return { kind: 'host-exit' }
1060
1078
  }
@@ -1430,7 +1448,11 @@ export async function runLinkCommand(args: string[], opts: ControlCommandOptions
1430
1448
 
1431
1449
  export async function runOpenCommand(
1432
1450
  args: string[],
1433
- opts: ControlCommandOptions = {},
1451
+ opts: ControlCommandOptions & {
1452
+ resources?: OpenCommandResources
1453
+ signal?: AbortSignal
1454
+ ownerPid?: number
1455
+ } = {},
1434
1456
  ): Promise<{ simId: string; launched: boolean } | undefined> {
1435
1457
  const parsed = parseBridgeCliArgs(args, {
1436
1458
  port: opts.port,
@@ -1581,7 +1603,14 @@ export async function runOpenCommand(
1581
1603
  }
1582
1604
  }
1583
1605
 
1584
- parsed.wsPort = await ensureBridgeForOpen(parsed.wsPort, parsed.explicitPort)
1606
+ opts.signal?.throwIfAborted()
1607
+ parsed.wsPort = await ensureBridgeForOpen(
1608
+ parsed.wsPort,
1609
+ parsed.explicitPort,
1610
+ opts.resources,
1611
+ )
1612
+ if (opts.resources) opts.resources.port = parsed.wsPort
1613
+ opts.signal?.throwIfAborted()
1585
1614
 
1586
1615
  // playwright is the browser-sim launcher. it owns an isolated profile and
1587
1616
  // can reap exactly its own browser tree. electron remains available for the
@@ -1890,7 +1919,7 @@ export async function runOpenCommand(
1890
1919
 
1891
1920
  const configuredBaseUrl = applyRNXConfigToUrl(baseUrl, runtimeConfig)
1892
1921
  const openUrl = await buildOpenUrl(target, configuredBaseUrl, token, buildShellUrlOpts)
1893
- const ownerPid = getStableOwnerPid()
1922
+ const ownerPid = opts.ownerPid ?? getStableOwnerPid()
1894
1923
  if (!ownerPid) {
1895
1924
  console.error(
1896
1925
  ' could not resolve a stable owning process; refusing unsupervised sim host',
@@ -1903,6 +1932,7 @@ export async function runOpenCommand(
1903
1932
  rnxExit(1)
1904
1933
  }
1905
1934
  const connectWait = resolveDriverConnectWaitOptions()
1935
+ opts.signal?.throwIfAborted()
1906
1936
  const result = await driver.launch({
1907
1937
  url: openUrl,
1908
1938
  // the shell serves the sim page but its WS bridge may have drifted off
@@ -1925,6 +1955,8 @@ export async function runOpenCommand(
1925
1955
  : undefined),
1926
1956
  connectTimeoutMs: connectWait.hostTimeoutMs,
1927
1957
  })
1958
+ if (opts.resources) opts.resources.driver = { ...result, driverId }
1959
+ opts.signal?.throwIfAborted()
1928
1960
  if (!result.launched) {
1929
1961
  console.error(` ${driver.name} driver: ${result.message}`)
1930
1962
  rnxExit(1)
@@ -1934,7 +1966,11 @@ export async function runOpenCommand(
1934
1966
  parsed.commandTimeoutMs,
1935
1967
  simMatchesToken,
1936
1968
  result.pid,
1937
- { attempts: connectWait.attempts, intervalMs: connectWait.intervalMs },
1969
+ {
1970
+ attempts: connectWait.attempts,
1971
+ intervalMs: connectWait.intervalMs,
1972
+ signal: opts.signal,
1973
+ },
1938
1974
  )
1939
1975
  if (outcome.kind === 'host-exit') {
1940
1976
  printDriverDiagnosticLogTail(result)
@@ -1955,6 +1991,7 @@ export async function runOpenCommand(
1955
1991
  rnxExit(1)
1956
1992
  }
1957
1993
  const match = outcome.sim
1994
+ if (opts.resources) opts.resources.simId = match.id
1958
1995
  saveCurrentSimId(match.id)
1959
1996
  signalDriverLaunchConnected(result)
1960
1997
  printOpenedSim(openUrl, match, `${driver.name} driver`, quiet)
@@ -0,0 +1,326 @@
1
+ import fs from 'node:fs'
2
+ import http from 'node:http'
3
+ import path from 'node:path'
4
+ import { WebSocket, WebSocketServer } from 'ws'
5
+ import { isLoopbackHost, resolveContrastBackendOrigin } from '../../src/backend-origin'
6
+ import { swiftGuestBundle } from '../../src/swift-guest'
7
+ import { SWIFT_HMR_PATH, SWIFT_PACKAGE_PATH } from '../../src/swift-submit'
8
+ import { authHeaderValue, resolveCliAuth } from '../auth'
9
+ import { PeachExit } from '../run-rnx'
10
+ import { createBridge } from '../ws-bridge'
11
+ import { runCloseCommand, runOpenCommand, type OpenCommandResources } from './control'
12
+ import type { SwiftEntryModuleArgs } from '../../../sootsim-swift/src/entryModule'
13
+
14
+ export async function runSwift(
15
+ args: string[],
16
+ opts: { port?: number; device?: string; driver?: string; headless?: boolean } = {},
17
+ ): Promise<number> {
18
+ const input = [...args]
19
+ const openArgs: string[] = []
20
+ let hostPort = 0
21
+ let uploadSource = false
22
+ const valueFlags = new Set([
23
+ '--device',
24
+ '--driver',
25
+ '--profile',
26
+ '--cdp-port',
27
+ '--os-version',
28
+ '--base-url',
29
+ ])
30
+ const booleanFlags = new Set([
31
+ '--headless',
32
+ '--headed',
33
+ '--ephemeral',
34
+ '--quiet',
35
+ '--proof',
36
+ ])
37
+ let packagePath = ''
38
+ for (let index = 0; index < input.length; index++) {
39
+ const arg = input[index]
40
+ if (arg === '--upload-source') uploadSource = true
41
+ else if (arg === '--host-port' || valueFlags.has(arg)) {
42
+ const value = input[++index]
43
+ if (!value || value.startsWith('-')) throw new Error(`${arg} requires a value`)
44
+ if (arg === '--host-port') {
45
+ hostPort = Number(value)
46
+ if (!Number.isInteger(hostPort) || hostPort < 1 || hostPort > 65535) {
47
+ throw new Error('--host-port must be a port from 1 to 65535')
48
+ }
49
+ } else openArgs.push(arg, value)
50
+ } else if (booleanFlags.has(arg)) openArgs.push(arg)
51
+ else if (arg.startsWith('-')) throw new Error(`unknown peach swift flag: ${arg}`)
52
+ else if (packagePath) throw new Error('peach swift expects one SwiftPM directory')
53
+ else packagePath = path.resolve(arg)
54
+ }
55
+ if (!packagePath)
56
+ throw new Error('usage: peach swift <SwiftPM-directory> --upload-source')
57
+ if (!uploadSource) {
58
+ throw new Error(
59
+ 'peach swift uploads the selected package sources and resources to the authenticated Contrast compiler. Pass --upload-source to consent to this directory and its watched edits.',
60
+ )
61
+ }
62
+ const auth = resolveCliAuth()
63
+ if (!auth || auth.kind === 'github') {
64
+ throw new Error(
65
+ 'peach swift needs `peach login` or PEACH_API_KEY before reading project sources',
66
+ )
67
+ }
68
+ const compileOrigin = resolveContrastBackendOrigin(
69
+ auth.kind === 'session' ? auth.origin : null,
70
+ )
71
+ packagePath = fs.realpathSync(packagePath)
72
+ if (
73
+ !fs.statSync(packagePath).isDirectory() ||
74
+ !fs.existsSync(path.join(packagePath, 'Package.swift'))
75
+ ) {
76
+ throw new Error('peach swift requires a directory containing Package.swift')
77
+ }
78
+ const host = await import('../../src/swift-host')
79
+ // collection preflights every input path before any source bytes are read.
80
+ host.swiftPackageWatchFiles(packagePath, undefined)
81
+ const packageName = path.basename(packagePath)
82
+ const guest = swiftGuestBundle(packageName)
83
+ const controller = new AbortController()
84
+ const resources: OpenCommandResources = {}
85
+ let metadata: (SwiftEntryModuleArgs & { hmrPath: string }) | null = null
86
+ let compileError = 'Swift compilation is starting'
87
+ let origin = ''
88
+ let requested = 0
89
+ let completed = 0
90
+ let building: Promise<void> | undefined
91
+ const sockets = new WebSocketServer({ noServer: true })
92
+ const send = (message: object) => {
93
+ const text = JSON.stringify(message)
94
+ for (const client of sockets.clients)
95
+ if (client.readyState === WebSocket.OPEN) client.send(text)
96
+ }
97
+ const refresh = (): Promise<void> => {
98
+ requested++
99
+ if (building) return building
100
+ building = (async () => {
101
+ while (!controller.signal.aborted && completed !== requested) {
102
+ const revision = requested
103
+ try {
104
+ const next = await host.swiftPackageMetadata(
105
+ packagePath,
106
+ packageName,
107
+ undefined,
108
+ origin,
109
+ {
110
+ origin: compileOrigin,
111
+ authorization: authHeaderValue(auth),
112
+ signal: controller.signal,
113
+ },
114
+ )
115
+ if (controller.signal.aborted) return
116
+ if (revision === requested) {
117
+ metadata = { ...next, hmrPath: `${origin}${SWIFT_HMR_PATH}` }
118
+ compileError = ''
119
+ send({ type: 'package', packageName, metadata })
120
+ console.log(` Swift artifact: ${next.hash}`)
121
+ }
122
+ } catch (error) {
123
+ if (controller.signal.aborted) return
124
+ if (revision === requested) {
125
+ compileError = error instanceof Error ? error.message : String(error)
126
+ console.error(compileError)
127
+ send({ type: 'error', packageName, message: compileError })
128
+ }
129
+ }
130
+ completed = revision
131
+ }
132
+ })().finally(() => {
133
+ building = undefined
134
+ })
135
+ return building
136
+ }
137
+ const validRequest = (req: http.IncomingMessage): boolean => {
138
+ if (req.headers.host !== new URL(origin).host) return false
139
+ if (!req.headers.origin) return true
140
+ try {
141
+ const requestOrigin = new URL(req.headers.origin)
142
+ return (
143
+ (requestOrigin.protocol === 'http:' || requestOrigin.protocol === 'https:') &&
144
+ isLoopbackHost(requestOrigin.hostname)
145
+ )
146
+ } catch {
147
+ return false
148
+ }
149
+ }
150
+ const server = http.createServer((req, res) => {
151
+ if (!validRequest(req)) {
152
+ res.statusCode = 403
153
+ res.end('forbidden Swift host origin')
154
+ return
155
+ }
156
+ res.setHeader('access-control-allow-origin', req.headers.origin ?? '*')
157
+ res.setHeader('vary', 'origin')
158
+ res.setHeader('cache-control', 'no-store')
159
+ if (req.method !== 'GET') {
160
+ res.statusCode = 405
161
+ res.end('GET required')
162
+ return
163
+ }
164
+ const pathname = new URL(req.url ?? '/', origin).pathname
165
+ if (pathname === '/__peach/swift-guest.js') {
166
+ res.setHeader('content-type', 'application/javascript')
167
+ res.end(guest)
168
+ } else if (pathname === `${SWIFT_PACKAGE_PATH}${encodeURIComponent(packageName)}`) {
169
+ if (!metadata) {
170
+ res.statusCode = 503
171
+ res.end(compileError)
172
+ return
173
+ }
174
+ res.setHeader('content-type', 'application/json')
175
+ res.end(JSON.stringify(metadata))
176
+ } else if (!host.serveSwiftPackageAsset(pathname, res)) {
177
+ res.statusCode = 404
178
+ res.end('unknown Swift host asset')
179
+ }
180
+ })
181
+ server.on('upgrade', (req, socket, head) => {
182
+ if (
183
+ !validRequest(req) ||
184
+ new URL(req.url ?? '/', origin).pathname !== SWIFT_HMR_PATH
185
+ ) {
186
+ socket.destroy()
187
+ return
188
+ }
189
+ sockets.handleUpgrade(req, socket, head, (client) => {
190
+ sockets.emit('connection', client, req)
191
+ client.send(
192
+ JSON.stringify(
193
+ metadata
194
+ ? { type: 'package', packageName, metadata }
195
+ : { type: 'error', packageName, message: compileError },
196
+ ),
197
+ )
198
+ if (metadata && compileError)
199
+ client.send(JSON.stringify({ type: 'error', packageName, message: compileError }))
200
+ })
201
+ })
202
+ const stop = () => controller.abort()
203
+ process.once('SIGINT', stop)
204
+ process.once('SIGTERM', stop)
205
+ let watcher: fs.FSWatcher | undefined
206
+ let code = 0
207
+ try {
208
+ await new Promise<void>((resolve, reject) => {
209
+ server.once('error', reject)
210
+ server.listen(hostPort, '127.0.0.1', () => {
211
+ server.removeListener('error', reject)
212
+ const address = server.address()
213
+ if (!address || typeof address === 'string') {
214
+ reject(new Error('Swift host has no loopback port'))
215
+ return
216
+ }
217
+ origin = `http://127.0.0.1:${address.port}`
218
+ resolve()
219
+ })
220
+ })
221
+ server.on('error', (error) => {
222
+ console.error(error.message)
223
+ code = 1
224
+ stop()
225
+ })
226
+ watcher = fs.watch(packagePath, { recursive: true }, (_event, file) => {
227
+ if (
228
+ file &&
229
+ file
230
+ .split(/[\\/]/)
231
+ .some(
232
+ (segment) =>
233
+ (segment.startsWith('.') &&
234
+ segment !== '.one-native-source.json' &&
235
+ segment !== '.one-native-source.swift.txt') ||
236
+ ['node_modules', 'ios', 'android', 'build', 'dist', 'types'].includes(
237
+ segment,
238
+ ),
239
+ )
240
+ )
241
+ return
242
+ void refresh()
243
+ })
244
+ watcher.on('error', (error) => {
245
+ console.error(error.message)
246
+ code = 1
247
+ stop()
248
+ })
249
+ console.log(
250
+ ` Swift source upload: ${packagePath} → ${compileOrigin}/api/swift-compile`,
251
+ )
252
+ console.log(` Swift preview host: ${origin}`)
253
+ await refresh()
254
+ controller.signal.throwIfAborted()
255
+ if (opts.device) openArgs.push('--device', opts.device)
256
+ if (opts.driver) openArgs.push('--driver', opts.driver)
257
+ if (opts.headless) openArgs.push('--headless')
258
+ await runOpenCommand(
259
+ [`${origin}/__peach/swift-guest.js`, '--new', '--no-hmr', ...openArgs],
260
+ {
261
+ port: opts.port,
262
+ resources,
263
+ signal: controller.signal,
264
+ ownerPid: process.pid,
265
+ },
266
+ )
267
+ await new Promise<void>((resolve) => {
268
+ if (controller.signal.aborted) resolve()
269
+ else controller.signal.addEventListener('abort', () => resolve(), { once: true })
270
+ })
271
+ } catch (error) {
272
+ if (!controller.signal.aborted) {
273
+ console.error(
274
+ ` peach swift failed: ${error instanceof Error ? error.message : String(error)}`,
275
+ )
276
+ code = error instanceof PeachExit ? error.code : 1
277
+ }
278
+ } finally {
279
+ controller.abort()
280
+ process.removeListener('SIGINT', stop)
281
+ process.removeListener('SIGTERM', stop)
282
+ watcher?.close()
283
+ await building
284
+ for (const client of sockets.clients) client.terminate()
285
+ sockets.close()
286
+ server.closeAllConnections()
287
+ await new Promise<void>((resolve) => server.close(() => resolve()))
288
+ if (resources.simId && resources.port) {
289
+ try {
290
+ await runCloseCommand(['--sim', resources.simId], { port: resources.port })
291
+ } catch (error) {
292
+ console.error(` Swift preview cleanup failed: ${String(error)}`)
293
+ code = 1
294
+ }
295
+ }
296
+ if (
297
+ resources.driver?.driverId === 'playwright' &&
298
+ resources.driver.launched &&
299
+ resources.driver.pid
300
+ ) {
301
+ try {
302
+ process.kill(resources.driver.pid, 'SIGTERM')
303
+ } catch (error) {
304
+ if (!(error instanceof Error && 'code' in error && error.code === 'ESRCH')) {
305
+ console.error(String(error))
306
+ code = 1
307
+ }
308
+ }
309
+ }
310
+ if (resources.bridge && !resources.bridgeReady) resources.bridge.kill('SIGTERM')
311
+ if (resources.bridge && resources.bridgeReady && resources.port) {
312
+ const bridge = createBridge(resources.port)
313
+ try {
314
+ if ((await bridge.listSims()).length === 0) resources.bridge.kill('SIGTERM')
315
+ else console.log(' preserving the bridge adopted by another simulator')
316
+ } catch (error) {
317
+ console.error(` cannot verify owned bridge cleanup: ${String(error)}`)
318
+ code = 1
319
+ } finally {
320
+ bridge.close()
321
+ }
322
+ }
323
+ host.disposeSwiftProject(packagePath)
324
+ }
325
+ return code
326
+ }
package/cli/main.ts CHANGED
@@ -551,6 +551,17 @@ try {
551
551
  }
552
552
  }
553
553
 
554
+ case 'swift': {
555
+ if (globalSimTarget) {
556
+ console.error(' peach swift owns a new preview and cannot target --sim')
557
+ await exitWithFlush(1)
558
+ }
559
+ const { runSwift } = await import('./commands/swift')
560
+ await exitWithFlush(
561
+ await runSwift(parsed.commandArgs, { port, device, driver, headless }),
562
+ )
563
+ }
564
+
554
565
  case 'preview': {
555
566
  const { runPreview } = await import('./commands/preview')
556
567
  await exitWithFlush(await runPreview(parsed.commandArgs))
@@ -124,7 +124,7 @@ export const OUTBOUND_ENDPOINTS: Record<string, OutboundEndpointDeclaration> = {
124
124
  'the origin already serving the app, or the absolute Contrast origin a node caller passes',
125
125
  preConsent: false,
126
126
  payload:
127
- 'the project .swift sources, packed bundle resources and images, plus the signed preview token or ccdn_ api key that authorizes the compile',
127
+ 'the selected project .swift sources, packed bundle resources and images, authorized with a preview token, stored session or API key; peach swift requires --upload-source for that root and its watched edits',
128
128
  },
129
129
  peach_cloud_sim: {
130
130
  category: 'explicit-user-action',
@@ -221,6 +221,10 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
221
221
  // compiling a project's .swift sources needs a swift toolchain, so the
222
222
  // bundler posts them to the app's own /api/swift-compile. it only fires with
223
223
  // a compile credential and only for a project that has .swift sources.
224
+ 'packages/peachsim/src/swift-host.ts :: fetch :: address.url': {
225
+ category: 'swift_compile',
226
+ count: 1,
227
+ },
224
228
  'packages/peachsim/src/swift-submit.ts :: fetch :: url': {
225
229
  category: 'swift_compile',
226
230
  count: 1,
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/beta.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/beta.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/beta.ts
4
4
  var IS_BETA = true;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
4
  var SIM_LONG_PRESS_MAX_MS = 5e3;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
4
  var REFUSED_PERFORM_STEP_TYPES = [
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/capture-contract.ts
4
4
  var RNX_SCREEN_CAPTURE_VERSION = 1;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/cloud-contract.ts
4
4
  var RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/cloud-test-args.ts
4
4
  var CLOUD_TEST_ARGS_MAX_COUNT = 32;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.638 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;