peachsim 0.1.919 → 0.1.921
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/commands/compat.ts +51 -9
- package/cli/commands/daemon.ts +35 -14
- package/cli/commands/flow.ts +25 -3
- package/cli/commands/maestro.ts +10 -2
- package/cli/commands/report-issue.ts +32 -16
- package/cli/commands/skills.ts +1 -1
- package/cli/commands/support.ts +3 -3
- package/cli/main.ts +5 -0
- package/cli/runtime-compat.ts +127 -0
- package/cli/ws-bridge.ts +14 -4
- 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/bundle-channel.cjs +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 +11 -3
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/expo-swift-bundle.cjs +1 -1
- package/dist-lib/expo-swift-metadata.cjs +1 -1
- package/dist-lib/expo-swift-metadata.mjs +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 +11 -3
- 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/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.mjs +1 -1
- 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 +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 +29 -4
- 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 +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 +1 -1
- package/dist-lib/vite.cjs +1 -1
- package/dist-lib/worklet-executable.cjs +1 -1
- package/dist-lib/worklet-executable.mjs +1 -1
- package/package.json +1 -1
package/cli/commands/compat.ts
CHANGED
|
@@ -14,11 +14,13 @@ import {
|
|
|
14
14
|
COMPAT_RESULT_CAVEAT,
|
|
15
15
|
formatDetailedScanLines,
|
|
16
16
|
formatScanCounts,
|
|
17
|
-
|
|
17
|
+
scanDepsWithData,
|
|
18
|
+
type CompatScanData,
|
|
18
19
|
type ScanResult,
|
|
19
20
|
} from '@peach/compat/web'
|
|
20
21
|
import { rnxPublicBrand } from '../../src/public-brand'
|
|
21
22
|
import { resolveRNXAppConfig } from '../app-config'
|
|
23
|
+
import { readRuntimeCompatibility } from '../runtime-compat'
|
|
22
24
|
import {
|
|
23
25
|
findProjectRoot,
|
|
24
26
|
scanRepository,
|
|
@@ -59,14 +61,34 @@ flags:
|
|
|
59
61
|
return
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
const { config } = await resolveRNXAppConfig(appDir)
|
|
65
|
+
const runtime = readRuntimeCompatibility(config?.runtimeVersion)
|
|
66
|
+
if (!runtime.data) {
|
|
67
|
+
if (args.includes('--json'))
|
|
68
|
+
console.log(
|
|
69
|
+
JSON.stringify({
|
|
70
|
+
runtimeVersion: runtime.runtimeVersion,
|
|
71
|
+
overallScore: null,
|
|
72
|
+
error: runtime.reason,
|
|
73
|
+
}),
|
|
74
|
+
)
|
|
75
|
+
else console.error(` ${runtime.reason}`)
|
|
76
|
+
process.exitCode = 1
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
const data = runtime.data
|
|
80
|
+
const scanDeps = (manifest: ProjectManifest) => scanDepsWithData(manifest, data)
|
|
81
|
+
const reportRuntime = (result: ScanResult, local: LocalNative[] | null) =>
|
|
82
|
+
report(args, result, local, runtime.runtimeVersion)
|
|
83
|
+
|
|
62
84
|
if (packageName) {
|
|
63
|
-
|
|
85
|
+
reportRuntime(scanOnePackage(appDir, pkg, packageName, runtime.data), null)
|
|
64
86
|
return
|
|
65
87
|
}
|
|
66
88
|
|
|
67
89
|
const here = scanDeps(installedManifest(appDir, pkg))
|
|
68
90
|
if (here.packages.length > 0 || appIndex >= 0) {
|
|
69
|
-
|
|
91
|
+
reportRuntime(here, await scanLocalNative(appDir, pkg))
|
|
70
92
|
return
|
|
71
93
|
}
|
|
72
94
|
|
|
@@ -84,8 +106,9 @@ flags:
|
|
|
84
106
|
|
|
85
107
|
if (elsewhere.length === 1) {
|
|
86
108
|
const only = elsewhere[0]
|
|
87
|
-
|
|
88
|
-
|
|
109
|
+
if (!args.includes('--json'))
|
|
110
|
+
console.log(`\n scanning ${relative(appDir, only.dir) || '.'}`)
|
|
111
|
+
reportRuntime(only.result, await scanLocalNative(resolve(only.dir), only.manifest))
|
|
89
112
|
return
|
|
90
113
|
}
|
|
91
114
|
if (elsewhere.length > 1) {
|
|
@@ -96,13 +119,31 @@ flags:
|
|
|
96
119
|
console.log()
|
|
97
120
|
return
|
|
98
121
|
}
|
|
99
|
-
|
|
122
|
+
reportRuntime(here, await scanLocalNative(appDir, pkg))
|
|
100
123
|
}
|
|
101
124
|
|
|
102
|
-
function report(
|
|
125
|
+
function report(
|
|
126
|
+
args: string[],
|
|
127
|
+
result: ScanResult,
|
|
128
|
+
local: LocalNative[] | null,
|
|
129
|
+
runtimeVersion: string | null,
|
|
130
|
+
): void {
|
|
103
131
|
if (args.includes('--json')) {
|
|
132
|
+
const overallScore = result.scoreSummary.assessed === 0 ? null : result.overallScore
|
|
104
133
|
console.log(
|
|
105
|
-
JSON.stringify(
|
|
134
|
+
JSON.stringify(
|
|
135
|
+
{
|
|
136
|
+
...result,
|
|
137
|
+
runtimeVersion,
|
|
138
|
+
overallScore,
|
|
139
|
+
scoreSummary: { ...result.scoreSummary, compatibility: overallScore },
|
|
140
|
+
implementationEstimate:
|
|
141
|
+
result.scoreSummary.assessed === 0 ? result.overallScore : null,
|
|
142
|
+
...(local ? { localNative: local } : {}),
|
|
143
|
+
},
|
|
144
|
+
null,
|
|
145
|
+
2,
|
|
146
|
+
),
|
|
106
147
|
)
|
|
107
148
|
return
|
|
108
149
|
}
|
|
@@ -343,11 +384,12 @@ function scanOnePackage(
|
|
|
343
384
|
appDir: string,
|
|
344
385
|
pkg: ProjectManifest,
|
|
345
386
|
packageName: string,
|
|
387
|
+
data: CompatScanData,
|
|
346
388
|
): ScanResult {
|
|
347
389
|
const installed = installedManifest(appDir, pkg)
|
|
348
390
|
const version =
|
|
349
391
|
installed.dependencies?.[packageName] ??
|
|
350
392
|
installed.devDependencies?.[packageName] ??
|
|
351
393
|
'*'
|
|
352
|
-
return
|
|
394
|
+
return scanDepsWithData({ dependencies: { [packageName]: version } }, data)
|
|
353
395
|
}
|
package/cli/commands/daemon.ts
CHANGED
|
@@ -223,7 +223,9 @@ export async function daemonInstall({ port, force }: { port: number; force: bool
|
|
|
223
223
|
const path = macPlistPath()
|
|
224
224
|
if (existsSync(path) && !force) {
|
|
225
225
|
console.log(` already installed at ${path}`)
|
|
226
|
-
|
|
226
|
+
const pid = await startMacDaemon(path)
|
|
227
|
+
console.log(` registered (state: running, pid ${pid})`)
|
|
228
|
+
console.log(` pass --force to overwrite the service configuration`)
|
|
227
229
|
return
|
|
228
230
|
}
|
|
229
231
|
await stopMacDaemonBridge(invocation)
|
|
@@ -321,8 +323,10 @@ function macServiceTarget(): string {
|
|
|
321
323
|
return `gui/${process.getuid!()}/${SERVICE_LABEL_MAC}`
|
|
322
324
|
}
|
|
323
325
|
|
|
324
|
-
function readMacDaemonState(
|
|
325
|
-
|
|
326
|
+
function readMacDaemonState(
|
|
327
|
+
target = macServiceTarget(),
|
|
328
|
+
): { state: string; pid: number | null } | null {
|
|
329
|
+
const result = spawnSync('launchctl', ['print', target], {
|
|
326
330
|
encoding: 'utf8',
|
|
327
331
|
})
|
|
328
332
|
if (result.status !== 0) return null
|
|
@@ -358,8 +362,11 @@ function macBridgeHasListener(pid: number, port: number): boolean {
|
|
|
358
362
|
return result.stdout.trim() === String(pid)
|
|
359
363
|
}
|
|
360
364
|
|
|
361
|
-
async function stopMacDaemonBridge(
|
|
362
|
-
|
|
365
|
+
export async function stopMacDaemonBridge(
|
|
366
|
+
invocation: RnxSelfInvocation,
|
|
367
|
+
target = macServiceTarget(),
|
|
368
|
+
): Promise<void> {
|
|
369
|
+
const service = readMacDaemonState(target)
|
|
363
370
|
const lock = readDaemonLockfile()
|
|
364
371
|
if (isDaemonLockfileFresh(lock) && lock.pid !== service?.pid) {
|
|
365
372
|
// a lockfile alone cannot authorize a signal after pid reuse.
|
|
@@ -380,7 +387,7 @@ async function stopMacDaemonBridge(invocation: RnxSelfInvocation): Promise<void>
|
|
|
380
387
|
}
|
|
381
388
|
process.kill(lock.pid, 'SIGTERM')
|
|
382
389
|
}
|
|
383
|
-
if (service) runLaunchctl(['bootout',
|
|
390
|
+
if (service) runLaunchctl(['bootout', target])
|
|
384
391
|
if (!lock) return
|
|
385
392
|
const deadline = Date.now() + 4000
|
|
386
393
|
while (Date.now() < deadline) {
|
|
@@ -390,22 +397,36 @@ async function stopMacDaemonBridge(invocation: RnxSelfInvocation): Promise<void>
|
|
|
390
397
|
throw new Error(`bridge pid ${lock.pid} did not release port ${lock.bridgePort}`)
|
|
391
398
|
}
|
|
392
399
|
|
|
393
|
-
async function startMacDaemon(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
400
|
+
export async function startMacDaemon(
|
|
401
|
+
path: string,
|
|
402
|
+
invocation = resolveDaemonInvocation(),
|
|
403
|
+
target = macServiceTarget(),
|
|
404
|
+
): Promise<number> {
|
|
405
|
+
const service = readMacDaemonState(target)
|
|
406
|
+
const lock = readDaemonLockfile()
|
|
407
|
+
if (
|
|
408
|
+
service?.state === 'running' &&
|
|
409
|
+
service.pid !== null &&
|
|
410
|
+
isDaemonLockfileFresh(lock) &&
|
|
411
|
+
lock.pid === service.pid &&
|
|
412
|
+
macBridgeHasListener(lock.pid, lock.bridgePort)
|
|
413
|
+
)
|
|
414
|
+
return service.pid
|
|
415
|
+
// reconcile an unloaded service or a bare bridge before launchd can create
|
|
416
|
+
// another process. start is idempotent when the service already owns the bridge.
|
|
417
|
+
await stopMacDaemonBridge(invocation, target)
|
|
418
|
+
runLaunchctl(['bootstrap', target.slice(0, target.lastIndexOf('/')), path])
|
|
399
419
|
// registration and an xpcproxy pid precede the actual bridge bind.
|
|
400
420
|
const deadline = Date.now() + 4000
|
|
401
421
|
while (Date.now() < deadline) {
|
|
402
|
-
const service = readMacDaemonState()
|
|
422
|
+
const service = readMacDaemonState(target)
|
|
403
423
|
const lock = readDaemonLockfile()
|
|
404
424
|
if (
|
|
405
425
|
service?.state === 'running' &&
|
|
406
426
|
service.pid !== null &&
|
|
407
427
|
isDaemonLockfileFresh(lock) &&
|
|
408
|
-
lock.pid === service.pid
|
|
428
|
+
lock.pid === service.pid &&
|
|
429
|
+
macBridgeHasListener(lock.pid, lock.bridgePort)
|
|
409
430
|
)
|
|
410
431
|
return service.pid
|
|
411
432
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
package/cli/commands/flow.ts
CHANGED
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
BRIDGE_VALUE_FLAGS,
|
|
27
27
|
createBridge,
|
|
28
28
|
createBridgeFromParsed,
|
|
29
|
+
DisconnectedSimError,
|
|
30
|
+
disconnectedSavedSimMessage,
|
|
29
31
|
parseBridgeCliArgs,
|
|
30
32
|
resolveBridgePortForPin,
|
|
31
33
|
} from '../ws-bridge'
|
|
@@ -480,6 +482,28 @@ flow extension:
|
|
|
480
482
|
rnxExit(1)
|
|
481
483
|
}
|
|
482
484
|
|
|
485
|
+
const effectiveOpenInNewSim =
|
|
486
|
+
openInNewSim || (wantElectron && parsedBridgeArgs.simIdSource !== 'flag')
|
|
487
|
+
const requestedSimId = parsedBridgeArgs.simId
|
|
488
|
+
// use the resolved playback bridge so an explicit shell stays in its world.
|
|
489
|
+
// a disconnected pin is a session failure, not a failed assertion in every flow.
|
|
490
|
+
if (requestedSimId && !effectiveOpenInNewSim) {
|
|
491
|
+
const probe = createBridgeFromParsed(parsedBridgeArgs)
|
|
492
|
+
try {
|
|
493
|
+
const sims = await probe.listSims()
|
|
494
|
+
if (!sims.some((sim) => sim.id === requestedSimId && sim.readyState === 'open')) {
|
|
495
|
+
throw new DisconnectedSimError(
|
|
496
|
+
parsedBridgeArgs.simIdSource === 'saved'
|
|
497
|
+
? disconnectedSavedSimMessage(requestedSimId)
|
|
498
|
+
: `sim ${requestedSimId} requested by --sim is not connected; ` +
|
|
499
|
+
'run `peach list` and retry with a connected sim, or open a fresh sim',
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
} finally {
|
|
503
|
+
probe.close()
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
483
507
|
const mode = wantElectron ? 'desktop companion' : 'bridge'
|
|
484
508
|
const appLabel = targetApp ? ` | target: ${targetApp}` : ''
|
|
485
509
|
console.log(`
|
|
@@ -488,8 +512,6 @@ flow extension:
|
|
|
488
512
|
`)
|
|
489
513
|
|
|
490
514
|
let openTarget = url || targetApp || ''
|
|
491
|
-
const effectiveOpenInNewSim =
|
|
492
|
-
openInNewSim || (wantElectron && parsedBridgeArgs.simIdSource !== 'flag')
|
|
493
515
|
const driverFlag = getFlag('--driver') || ''
|
|
494
516
|
let closeFlowOwnedSimAfterRun = false
|
|
495
517
|
const ownedSimIds = new Set<string>()
|
|
@@ -571,7 +593,7 @@ flow extension:
|
|
|
571
593
|
// (getStableOwnerPid -> findTopAncestor), which is the shell, agent
|
|
572
594
|
// session, or runner daemon, not the run. that pid can live for days, so
|
|
573
595
|
// a launched sim nobody closes stays open and keeps its flow lock.
|
|
574
|
-
closeFlowOwnedSimAfterRun = opened?.launched === true
|
|
596
|
+
closeFlowOwnedSimAfterRun = opened?.launched === true
|
|
575
597
|
if (closeFlowOwnedSimAfterRun && opened) ownedSimIds.add(opened.simId)
|
|
576
598
|
}
|
|
577
599
|
|
package/cli/commands/maestro.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from '../maestro-workspace'
|
|
31
31
|
import { registerRun, shouldRegisterRun, stepSummaryFromTrace } from '../run-registry'
|
|
32
32
|
import { RnxExit } from '../run-rnx'
|
|
33
|
+
import { DisconnectedSimError } from '../ws-bridge'
|
|
33
34
|
import {
|
|
34
35
|
findUnexpectedFlowArgs,
|
|
35
36
|
FLOW_ARG_VALUE_FLAGS,
|
|
@@ -478,7 +479,6 @@ export async function runMaestro(
|
|
|
478
479
|
frontmatter: FlowFrontmatter
|
|
479
480
|
}> = []
|
|
480
481
|
for (const [flowIndex, file] of files.entries()) {
|
|
481
|
-
console.log(`\n ▶ ${path.relative(cwd, file)}`)
|
|
482
482
|
const playbackArgs = [file, ...forwardArgs]
|
|
483
483
|
let flowTestDir: string | null = null
|
|
484
484
|
if (testOutputRoot) {
|
|
@@ -512,8 +512,16 @@ export async function runMaestro(
|
|
|
512
512
|
let exit = 0
|
|
513
513
|
const startedAt = Date.now()
|
|
514
514
|
try {
|
|
515
|
-
exit = await runFlowPlayback(playbackArgs
|
|
515
|
+
exit = await runFlowPlayback(playbackArgs, {
|
|
516
|
+
beforeSteps: async () => {
|
|
517
|
+
console.log(`\n ▶ ${path.relative(cwd, file)}`)
|
|
518
|
+
},
|
|
519
|
+
})
|
|
516
520
|
} catch (err) {
|
|
521
|
+
if (err instanceof DisconnectedSimError) {
|
|
522
|
+
console.error(` error: ${err.message}`)
|
|
523
|
+
return 1
|
|
524
|
+
}
|
|
517
525
|
// rnxExit is a deliberate exit that already printed its own reason.
|
|
518
526
|
// reporting the sentinel's own text on top of it reads like a second,
|
|
519
527
|
// unrelated failure.
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
import { existsSync } from 'node:fs'
|
|
4
4
|
import { resolve } from 'node:path'
|
|
5
5
|
import { getCliVersion } from '../../src/cli-version'
|
|
6
|
-
import { readActiveRuntime } from '../../src/home-paths'
|
|
7
6
|
import { authHeaderValue, resolveCliAuth } from '../auth'
|
|
8
7
|
import { confirm } from '../prompt'
|
|
8
|
+
import { readRuntimeCompatibility } from '../runtime-compat'
|
|
9
9
|
import { installedManifest, readProjectManifest } from './detect'
|
|
10
10
|
import {
|
|
11
11
|
createProjectReport,
|
|
@@ -41,6 +41,7 @@ export type ReportIssuePayload = {
|
|
|
41
41
|
platform: NodeJS.Platform
|
|
42
42
|
arch: string
|
|
43
43
|
compatibility: ScanResult | null
|
|
44
|
+
compatibilityUnavailable: string | null
|
|
44
45
|
projectReport: ProjectReport
|
|
45
46
|
}
|
|
46
47
|
}
|
|
@@ -120,6 +121,7 @@ export function parseReportIssueOptions(
|
|
|
120
121
|
export async function toolchainContext(appDir: string) {
|
|
121
122
|
const manifest = readProjectManifest(appDir)
|
|
122
123
|
const installed = manifest ? installedManifest(appDir, manifest) : null
|
|
124
|
+
const runtime = readRuntimeCompatibility()
|
|
123
125
|
const safeVersions = (deps: Record<string, string> | undefined) =>
|
|
124
126
|
deps &&
|
|
125
127
|
Object.fromEntries(
|
|
@@ -128,17 +130,20 @@ export async function toolchainContext(appDir: string) {
|
|
|
128
130
|
sanitizeProjectReportVersion(version),
|
|
129
131
|
]),
|
|
130
132
|
)
|
|
131
|
-
const compatibility =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
const compatibility =
|
|
134
|
+
installed && runtime.data
|
|
135
|
+
? (await import('@peach/compat/web')).scanDepsWithData(
|
|
136
|
+
{
|
|
137
|
+
name: installed.name ? sanitizeProjectReportName(installed.name) : undefined,
|
|
138
|
+
dependencies: safeVersions(installed.dependencies),
|
|
139
|
+
devDependencies: safeVersions(installed.devDependencies),
|
|
140
|
+
},
|
|
141
|
+
runtime.data,
|
|
142
|
+
)
|
|
143
|
+
: null
|
|
138
144
|
const cliVersion = sanitizeProjectReportVersion(getCliVersion())
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
? sanitizeProjectReportVersion(activeRuntime)
|
|
145
|
+
const runtimeVersion = runtime.runtimeVersion
|
|
146
|
+
? sanitizeProjectReportVersion(runtime.runtimeVersion)
|
|
142
147
|
: null
|
|
143
148
|
return {
|
|
144
149
|
activeApp:
|
|
@@ -151,6 +156,7 @@ export async function toolchainContext(appDir: string) {
|
|
|
151
156
|
platform: process.platform,
|
|
152
157
|
arch: process.arch,
|
|
153
158
|
compatibility,
|
|
159
|
+
compatibilityUnavailable: runtime.reason,
|
|
154
160
|
}
|
|
155
161
|
}
|
|
156
162
|
|
|
@@ -174,7 +180,7 @@ export async function createReportIssuePayload(
|
|
|
174
180
|
|
|
175
181
|
// where support requests go and who they come from: the login's origin and
|
|
176
182
|
// bearer, and the account a `peach login --account` sign-in chose. a report
|
|
177
|
-
//
|
|
183
|
+
// needs a sign-in so the account can read its replies later.
|
|
178
184
|
export function supportEndpoint(): {
|
|
179
185
|
origin: string
|
|
180
186
|
headers: Record<string, string>
|
|
@@ -193,8 +199,11 @@ export function supportEndpoint(): {
|
|
|
193
199
|
return { origin, headers, signedIn: true }
|
|
194
200
|
}
|
|
195
201
|
|
|
196
|
-
function compatibilitySummary(
|
|
197
|
-
|
|
202
|
+
function compatibilitySummary(
|
|
203
|
+
scan: ScanResult | null,
|
|
204
|
+
unavailable: string | null,
|
|
205
|
+
): string {
|
|
206
|
+
if (!scan) return unavailable ?? 'no package.json found; no compatibility scan attached'
|
|
198
207
|
const attention = scan.packages.filter(
|
|
199
208
|
(item) => item.status !== 'full' && item.status !== 'not-relevant',
|
|
200
209
|
).length
|
|
@@ -227,6 +236,14 @@ export async function runReportIssue(
|
|
|
227
236
|
return 1
|
|
228
237
|
}
|
|
229
238
|
|
|
239
|
+
const endpoint = supportEndpoint()
|
|
240
|
+
if (!options.dryRun && !endpoint.signedIn) {
|
|
241
|
+
dependencies.stderr(
|
|
242
|
+
' sign in first with `peach login`, or set PEACH_API_KEY. Nothing was sent.\n',
|
|
243
|
+
)
|
|
244
|
+
return 1
|
|
245
|
+
}
|
|
246
|
+
|
|
230
247
|
let payload: ReportIssuePayload
|
|
231
248
|
try {
|
|
232
249
|
payload = await createReportIssuePayload(options, dependencies.now())
|
|
@@ -242,7 +259,7 @@ export async function runReportIssue(
|
|
|
242
259
|
dependencies.stdout(
|
|
243
260
|
`\n peach issue report preview\n\n` +
|
|
244
261
|
` description: ${payload.note}\n` +
|
|
245
|
-
` compatibility: ${compatibilitySummary(payload.bundle.compatibility)}\n` +
|
|
262
|
+
` compatibility: ${compatibilitySummary(payload.bundle.compatibility, payload.bundle.compatibilityUnavailable)}\n` +
|
|
246
263
|
` environment: CLI ${payload.bundle.cliVersion}, runtime ${
|
|
247
264
|
payload.bundle.runtimeVersion ?? 'not installed'
|
|
248
265
|
}, ${payload.bundle.platform}/${payload.bundle.arch}\n\n` +
|
|
@@ -273,7 +290,6 @@ export async function runReportIssue(
|
|
|
273
290
|
}
|
|
274
291
|
}
|
|
275
292
|
|
|
276
|
-
const endpoint = supportEndpoint()
|
|
277
293
|
const headers = { ...endpoint.headers, 'content-type': 'application/json' }
|
|
278
294
|
|
|
279
295
|
let response: Response
|
package/cli/commands/skills.ts
CHANGED
|
@@ -598,7 +598,7 @@ async function runInstall(args: string[]) {
|
|
|
598
598
|
)
|
|
599
599
|
rnxExit(1)
|
|
600
600
|
}
|
|
601
|
-
console.log(' done.
|
|
601
|
+
console.log(' done. skills are installed and available to your agent.\n')
|
|
602
602
|
}
|
|
603
603
|
|
|
604
604
|
function runShow(args: string[]) {
|
package/cli/commands/support.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//
|
|
5
5
|
// peach support ask "<question>" [--no-wait] [--wait <minutes>] [--json]
|
|
6
6
|
// peach support report [--app <dir>] [--dry-run] [--yes] "<description>"
|
|
7
|
-
// peach support status [--new] [--json]
|
|
7
|
+
// peach support status [--id <id>] [--new] [--json]
|
|
8
8
|
// peach support fund [--amount <usd>]
|
|
9
9
|
|
|
10
10
|
import { resolve } from 'node:path'
|
|
@@ -41,7 +41,7 @@ const USAGE =
|
|
|
41
41
|
' usage:\n' +
|
|
42
42
|
' peach support ask "<question>" [--no-wait] [--wait <minutes>] [--json]\n' +
|
|
43
43
|
' peach support report [--app <dir>] [--dry-run] [--yes] "<description>"\n' +
|
|
44
|
-
' peach support status [--new] [--json]\n' +
|
|
44
|
+
' peach support status [--id <id>] [--new] [--json]\n' +
|
|
45
45
|
' peach support fund [--amount <usd>]\n'
|
|
46
46
|
|
|
47
47
|
function signInHint(): number {
|
|
@@ -341,7 +341,7 @@ async function runStatus(args: string[]): Promise<number> {
|
|
|
341
341
|
if (!supportEndpoint().signedIn) return signInHint()
|
|
342
342
|
let items: Item[]
|
|
343
343
|
try {
|
|
344
|
-
items = await fetchItems()
|
|
344
|
+
items = await fetchItems(flagValue(args, '--id') ?? undefined)
|
|
345
345
|
} catch (error) {
|
|
346
346
|
process.stderr.write(
|
|
347
347
|
` status failed: ${error instanceof Error ? error.message : error}\n`,
|
package/cli/main.ts
CHANGED
|
@@ -556,6 +556,11 @@ try {
|
|
|
556
556
|
}
|
|
557
557
|
|
|
558
558
|
case 'remote': {
|
|
559
|
+
if (parsed.commandArgs.length === 0) {
|
|
560
|
+
const { printCommandHelp } = await import('./help')
|
|
561
|
+
printCommandHelp('remote')
|
|
562
|
+
await exitWithFlush(0)
|
|
563
|
+
}
|
|
559
564
|
if (globalSimTarget) {
|
|
560
565
|
console.error(
|
|
561
566
|
` ${rnxPublicBrand.commandName} remote creates a new simulator and cannot target --sim`,
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { readActiveRuntime, runtimeDir } from '../src/home-paths'
|
|
4
|
+
import type { CompatScanData } from '@peach/compat/web'
|
|
5
|
+
|
|
6
|
+
type Outcomes = NonNullable<
|
|
7
|
+
CompatScanData['registry'][string]['versions'][number]['outcomes']
|
|
8
|
+
>
|
|
9
|
+
|
|
10
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
11
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
12
|
+
}
|
|
13
|
+
function finite(value: unknown): value is number {
|
|
14
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0
|
|
15
|
+
}
|
|
16
|
+
function measure(value: unknown): boolean {
|
|
17
|
+
return (
|
|
18
|
+
record(value) &&
|
|
19
|
+
[
|
|
20
|
+
'passedWeight',
|
|
21
|
+
'failedWeight',
|
|
22
|
+
'unassessedWeight',
|
|
23
|
+
'totalWeight',
|
|
24
|
+
'assessedWeight',
|
|
25
|
+
'assessed',
|
|
26
|
+
].every((key) => finite(value[key])) &&
|
|
27
|
+
(value.compatibility === null ||
|
|
28
|
+
(finite(value.compatibility) && value.compatibility <= 1))
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
function platforms(value: unknown): boolean {
|
|
32
|
+
return record(value) && measure(value.ios) && measure(value.android)
|
|
33
|
+
}
|
|
34
|
+
function weightings(value: unknown): boolean {
|
|
35
|
+
return record(value) && platforms(value.strict) && platforms(value.usage)
|
|
36
|
+
}
|
|
37
|
+
function outcomes(value: unknown): value is Outcomes {
|
|
38
|
+
if (
|
|
39
|
+
!record(value) ||
|
|
40
|
+
typeof value.exactTestedVersion !== 'string' ||
|
|
41
|
+
typeof value.supportedVersionRange !== 'string' ||
|
|
42
|
+
!finite(value.unmatchedReferenceCount) ||
|
|
43
|
+
!platforms(value.byPlatform) ||
|
|
44
|
+
!weightings(value.byWeighting)
|
|
45
|
+
)
|
|
46
|
+
return false
|
|
47
|
+
const last = value.lastMeasured
|
|
48
|
+
return (
|
|
49
|
+
last === null ||
|
|
50
|
+
(record(last) &&
|
|
51
|
+
weightings(last.byWeighting) &&
|
|
52
|
+
(last.sourceCommit === null || typeof last.sourceCommit === 'string') &&
|
|
53
|
+
typeof last.capturedAt === 'string' &&
|
|
54
|
+
Array.isArray(last.receipts) &&
|
|
55
|
+
last.receipts.every((id) => typeof id === 'string'))
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
function scanData(value: unknown): value is CompatScanData {
|
|
59
|
+
if (
|
|
60
|
+
!record(value) ||
|
|
61
|
+
!record(value.registry) ||
|
|
62
|
+
!Array.isArray(value.unsupported) ||
|
|
63
|
+
!value.unsupported.every((name) => typeof name === 'string')
|
|
64
|
+
)
|
|
65
|
+
return false
|
|
66
|
+
return Object.values(value.registry).every(
|
|
67
|
+
(entry) =>
|
|
68
|
+
record(entry) &&
|
|
69
|
+
(entry.stubType === 'native' || entry.stubType === 'build-only') &&
|
|
70
|
+
Array.isArray(entry.versions) &&
|
|
71
|
+
entry.versions.every(
|
|
72
|
+
(version) =>
|
|
73
|
+
record(version) &&
|
|
74
|
+
typeof version.range === 'string' &&
|
|
75
|
+
typeof version.note === 'string' &&
|
|
76
|
+
(version.coverage === null ||
|
|
77
|
+
(finite(version.coverage) && version.coverage <= 1)) &&
|
|
78
|
+
(version.estimate === undefined ||
|
|
79
|
+
(finite(version.estimate) && version.estimate <= 1)) &&
|
|
80
|
+
(version.coverageSource === undefined ||
|
|
81
|
+
version.coverageSource === 'estimated' ||
|
|
82
|
+
(version.coverageSource === 'measured' && outcomes(version.outcomes))) &&
|
|
83
|
+
(version.outcomes === undefined || outcomes(version.outcomes)) &&
|
|
84
|
+
['working', 'missing'].every(
|
|
85
|
+
(key) => version[key] === undefined || typeof version[key] === 'string',
|
|
86
|
+
),
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// only the selected runtime can describe its compatibility. older runtimes
|
|
92
|
+
// without a snapshot remain explicitly unavailable rather than borrowing the CLI's data.
|
|
93
|
+
export function readRuntimeCompatibility(runtimeVersion = readActiveRuntime()): {
|
|
94
|
+
runtimeVersion: string | null
|
|
95
|
+
data: CompatScanData | null
|
|
96
|
+
reason: string | null
|
|
97
|
+
} {
|
|
98
|
+
if (!runtimeVersion)
|
|
99
|
+
return {
|
|
100
|
+
runtimeVersion,
|
|
101
|
+
data: null,
|
|
102
|
+
reason: 'no runtime selected; run `peach runtime install`',
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const snapshot: unknown = JSON.parse(
|
|
106
|
+
readFileSync(join(runtimeDir(runtimeVersion), 'compatibility.json'), 'utf8'),
|
|
107
|
+
)
|
|
108
|
+
if (
|
|
109
|
+
!record(snapshot) ||
|
|
110
|
+
snapshot.runtimeVersion !== runtimeVersion ||
|
|
111
|
+
!scanData(snapshot.data)
|
|
112
|
+
) {
|
|
113
|
+
return {
|
|
114
|
+
runtimeVersion,
|
|
115
|
+
data: null,
|
|
116
|
+
reason: 'runtime compatibility snapshot is invalid or belongs to another runtime',
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { runtimeVersion, data: snapshot.data, reason: null }
|
|
120
|
+
} catch {
|
|
121
|
+
return {
|
|
122
|
+
runtimeVersion,
|
|
123
|
+
data: null,
|
|
124
|
+
reason: 'runtime compatibility snapshot is unavailable; run `peach upgrade`',
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
package/cli/ws-bridge.ts
CHANGED
|
@@ -418,6 +418,15 @@ export interface BridgeSimInfo {
|
|
|
418
418
|
meta?: Record<string, unknown>
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
+
export class DisconnectedSimError extends Error {}
|
|
422
|
+
|
|
423
|
+
export function disconnectedSavedSimMessage(simId: string, detail = ''): string {
|
|
424
|
+
return (
|
|
425
|
+
`saved sim ${simId} is not connected${detail}; commands resume when it reconnects, ` +
|
|
426
|
+
'or run `peach list` and `peach use <sim>`, or open a fresh sim'
|
|
427
|
+
)
|
|
428
|
+
}
|
|
429
|
+
|
|
421
430
|
// a lock owner is either a friendly cli label ("active user") or a raw cli
|
|
422
431
|
// identity key ("CLAUDE_CODE_SESSION_ID:<uuid>", "gppid-1234"). keep the list
|
|
423
432
|
// readable by collapsing a "<source>:<value>" identity to a short form that
|
|
@@ -846,10 +855,11 @@ export function createBridge(wsPort: number, opts: WsBridgeOptions = {}): WsBrid
|
|
|
846
855
|
// is how agents end up recording the wrong app. the pin stays: a
|
|
847
856
|
// page whose socket dropped reconnects under the same id, and
|
|
848
857
|
// clearing the pin then would hand every later command to primary.
|
|
849
|
-
throw new
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
858
|
+
throw new DisconnectedSimError(
|
|
859
|
+
disconnectedSavedSimMessage(
|
|
860
|
+
sentSimId,
|
|
861
|
+
message.slice(missingSimMessage.length),
|
|
862
|
+
),
|
|
853
863
|
)
|
|
854
864
|
}
|
|
855
865
|
throw error
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|