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.
- package/cli/command-registry.ts +1 -0
- package/cli/commands/control.ts +42 -5
- package/cli/commands/swift.ts +326 -0
- package/cli/main.ts +11 -0
- package/cli/outbound-endpoints.ts +5 -1
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- 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/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cli-constants.cjs +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 +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +1 -1
- 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 +13 -4
- 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 +1 -1
- package/dist-lib/menu.mjs +1 -1
- 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 +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/native-app-identity.cjs +1 -1
- package/dist-lib/native-app-identity.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +48 -33
- package/dist-lib/swift-compile.cjs +1 -1
- package/dist-lib/swift-compile.mjs +1 -1
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +89 -8
- package/dist-lib/swift-submit.mjs +78 -8
- package/dist-lib/swift.cjs +16 -33308
- package/dist-lib/vite-swift.cjs +422 -247
- package/dist-lib/vite.cjs +13 -4
- package/package.json +1 -1
- package/skills/contrast/SKILL.md +1 -0
- package/src/assetCatalogContract.ts +16 -0
- package/src/metro-runtime-source.ts +15 -0
- package/src/swift-guest.ts +51 -0
- package/src/swift-host.ts +900 -0
- package/src/swift-submit.ts +97 -6
- package/src/swift.ts +36 -8
- package/src/swiftContract.ts +52 -0
- package/src/vite-plugin-one.ts +4 -3
- package/src/vite-plugin-swift.ts +43 -755
package/cli/command-registry.ts
CHANGED
package/cli/commands/control.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
{
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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/cloud.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
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;
|