peachsim 0.1.920 → 0.1.922
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/cli/commands/compat.ts +51 -9
- package/cli/commands/daemon.ts +35 -14
- package/cli/commands/flow.ts +7 -2
- package/cli/commands/maestro-generate.ts +292 -98
- package/cli/commands/maestro.ts +51 -29
- 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/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 +1 -1
- 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 +1 -1
- 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 +16 -1
- 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/skills/peach-test/SKILL.md +8 -1
package/README.md
CHANGED
|
@@ -519,12 +519,17 @@ Maestro is Peach's single YAML test surface. Point it at your existing
|
|
|
519
519
|
`.maestro/` directory, generate a test, or author one from live actions:
|
|
520
520
|
|
|
521
521
|
```sh
|
|
522
|
-
peach maestro # discover .maestro/
|
|
522
|
+
peach maestro # discover .maestro/ workspace
|
|
523
523
|
peach maestro test .maestro/ # explicit
|
|
524
524
|
peach maestro generate "verify login"
|
|
525
525
|
peach maestro --list-compat # see the verb support matrix
|
|
526
526
|
```
|
|
527
527
|
|
|
528
|
+
Directory discovery selects root YAML flows by default, as Maestro does.
|
|
529
|
+
For nested suites, pass their folder, such as `.maestro/flows`, or add
|
|
530
|
+
`config.yaml` with `flows: ["flows/**"]`. Keep shared subflows outside those
|
|
531
|
+
globs; `runFlow` can still load them.
|
|
532
|
+
|
|
528
533
|
Tap settling compares consecutive full-screen pixels within Maestro's iOS
|
|
529
534
|
three-second budget, while also draining screen transitions, native motion and
|
|
530
535
|
image loads. Periodic caret paints do not require a continuous stillness window,
|
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
|
@@ -11,6 +11,7 @@ import { applyPeachConfigToUrl, type PeachConfig } from '../../src/config'
|
|
|
11
11
|
import { printBridgeFailureDiagnostics } from '../bridge-diagnostics'
|
|
12
12
|
import {
|
|
13
13
|
SootSimBridgeFlowRunner,
|
|
14
|
+
type MaestroStep,
|
|
14
15
|
type SootSimFlowProfileResult,
|
|
15
16
|
type SootSimFlowTraceStep,
|
|
16
17
|
} from '../bridge-flow-runner'
|
|
@@ -217,7 +218,10 @@ export async function runFlowPlayback(
|
|
|
217
218
|
// beforeSteps runs once the flow is on its target and about to run its first
|
|
218
219
|
// step. opening a frontmatter `app:` navigates the sim, which wipes in-page
|
|
219
220
|
// state, so a caller capturing the run itself (film) starts here.
|
|
220
|
-
hooks: {
|
|
221
|
+
hooks: {
|
|
222
|
+
beforeSteps?: () => Promise<void>
|
|
223
|
+
runSteps?: (driver: SootSimBridgeFlowRunner, steps: MaestroStep[]) => Promise<void>
|
|
224
|
+
} = {},
|
|
221
225
|
): Promise<number> {
|
|
222
226
|
lastPreviewUploadResult = null
|
|
223
227
|
lastPreviewUploadError = null
|
|
@@ -782,7 +786,8 @@ flow extension:
|
|
|
782
786
|
}
|
|
783
787
|
}
|
|
784
788
|
await hooks.beforeSteps?.()
|
|
785
|
-
await
|
|
789
|
+
if (hooks.runSteps) await hooks.runSteps(driver, steps)
|
|
790
|
+
else await driver.runFlow(steps, { skipPlan: dryRun })
|
|
786
791
|
if (profile) {
|
|
787
792
|
profileResult = await driver.stopProfile()
|
|
788
793
|
}
|