peachsim 0.1.630 → 0.1.632
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 +9 -1
- package/cli/app-fonts.ts +23 -4
- package/cli/bridge-flow-runner.ts +35 -28
- package/cli/command-registry.ts +1 -0
- package/cli/commands/compat.ts +11 -60
- package/cli/commands/control.ts +12 -5
- package/cli/commands/detect.ts +289 -26
- package/cli/commands/inspect/core.ts +1 -1
- package/cli/commands/inspect.ts +4 -0
- package/cli/commands/maestro.ts +8 -2
- package/cli/commands/project-report.ts +386 -0
- package/cli/commands/projectReportContract.ts +42 -0
- package/cli/commands/report-issue.ts +38 -54
- package/cli/commands/setup.ts +6 -4
- package/cli/commands/telemetry.ts +8 -3
- package/cli/commands/test.ts +374 -133
- package/cli/drivers/playwright-core-pin.ts +10 -0
- package/cli/drivers/playwright-provisioning.ts +36 -1
- package/cli/drivers/playwright.ts +26 -19
- package/cli/main.ts +11 -1
- package/cli/outbound-endpoints.ts +2 -1
- package/cli/privacy.ts +1 -2
- package/cli/runtime-diagnostics.ts +51 -0
- package/cli/shell-init.ts +6 -1
- package/cli/telemetry.ts +78 -7
- package/cli/ws-bridge.ts +8 -2
- package/detox/index.ts +3 -8
- 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 +17 -5
- package/dist-lib/cloud-test-args.mjs +17 -5
- 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 +2 -7
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +13 -5
- package/dist-lib/host/bridge-host.cjs +157 -113
- 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 -2
- 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 +11 -1
- package/dist-lib/menu.mjs +11 -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 +3 -1
- package/dist-lib/sdk.mjs +2 -1
- package/dist-lib/skills.cjs +700 -329
- 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.cjs +443 -173
- package/dist-lib/vite-swift.cjs +1 -1
- package/dist-lib/vite.cjs +1 -1
- package/package.json +2 -1
- package/skills/contrast/SKILL.md +1 -0
- package/src/cloud-test-args.ts +18 -6
- package/src/home-paths.ts +19 -9
- package/src/menu.ts +11 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// playwright driver — launches Playwright's Google Chrome for Testing via the
|
|
2
|
-
// `playwright` package.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// `playwright` package. the package resolves from the current working
|
|
3
|
+
// directory, then the CLI's own install tree, then the pinned playwright-core
|
|
4
|
+
// the CLI provisions into its data dir on first launch, so the driver is
|
|
5
|
+
// always available.
|
|
6
6
|
//
|
|
7
7
|
// the browser runs in a *detached* child process — this CLI re-invoked as
|
|
8
8
|
// its own `__internal playwright-host` (see cli/internal-child.ts) — rather
|
|
@@ -22,7 +22,13 @@ import { ensureProfile, playwrightProfileUserDataDir } from '../../src/profiles'
|
|
|
22
22
|
import { RNX_INTERNAL_CHILDREN, RNX_INTERNAL_COMMAND } from '../internal-child'
|
|
23
23
|
import { getProcessComm } from '../parent-pid'
|
|
24
24
|
import { rnxSelfInvocation } from '../self-invocation'
|
|
25
|
-
import {
|
|
25
|
+
import { PINNED_PLAYWRIGHT_CORE } from './playwright-core-pin'
|
|
26
|
+
import {
|
|
27
|
+
ensurePlaywrightBrowser,
|
|
28
|
+
provisionPlaywright,
|
|
29
|
+
resolvePlaywright,
|
|
30
|
+
type ResolvedPlaywright,
|
|
31
|
+
} from './playwright-provisioning'
|
|
26
32
|
import type {
|
|
27
33
|
Driver,
|
|
28
34
|
DriverAvailability,
|
|
@@ -32,16 +38,12 @@ import type {
|
|
|
32
38
|
|
|
33
39
|
function probe(): DriverAvailability {
|
|
34
40
|
const resolved = resolvePlaywright()
|
|
35
|
-
if (!resolved) {
|
|
36
|
-
return {
|
|
37
|
-
available: false,
|
|
38
|
-
reason: 'playwright not installed in the current workspace',
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
41
|
return {
|
|
42
42
|
available: true,
|
|
43
43
|
reason: null,
|
|
44
|
-
detail:
|
|
44
|
+
detail: resolved
|
|
45
|
+
? `resolved via ${resolved.spec}@${resolved.version}`
|
|
46
|
+
: `installs playwright-core@${PINNED_PLAYWRIGHT_CORE.version} on first launch`,
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
|
|
@@ -75,13 +77,6 @@ function resolveUserDataDir(opts: DriverLaunchOptions): string {
|
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
async function launch(opts: DriverLaunchOptions): Promise<DriverLaunchResult> {
|
|
78
|
-
const resolved = resolvePlaywright()
|
|
79
|
-
if (!resolved) {
|
|
80
|
-
return {
|
|
81
|
-
launched: false,
|
|
82
|
-
message: 'playwright not installed — run `bun add -D playwright` first',
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
80
|
if (!opts.url) {
|
|
86
81
|
return { launched: false, message: 'playwright driver requires a target url' }
|
|
87
82
|
}
|
|
@@ -93,6 +88,18 @@ async function launch(opts: DriverLaunchOptions): Promise<DriverLaunchResult> {
|
|
|
93
88
|
}
|
|
94
89
|
}
|
|
95
90
|
|
|
91
|
+
let resolved: ResolvedPlaywright
|
|
92
|
+
try {
|
|
93
|
+
resolved = await provisionPlaywright()
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return {
|
|
96
|
+
launched: false,
|
|
97
|
+
message: `playwright provisioning failed: ${
|
|
98
|
+
error instanceof Error ? error.message : String(error)
|
|
99
|
+
}`,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
96
103
|
// keep browser discovery, installation, and the detached host on one exact
|
|
97
104
|
// cache contract even if another caller mutates process.env during launch.
|
|
98
105
|
const playwrightEnv = { ...process.env }
|
package/cli/main.ts
CHANGED
|
@@ -22,7 +22,10 @@ import { IS_STANDALONE } from './standalone'
|
|
|
22
22
|
// "command timed out after 10s" message and is uncopyable for a bug report.
|
|
23
23
|
// rejections that don't match this shape still get the verbose stack so we
|
|
24
24
|
// don't paper over real bugs.
|
|
25
|
-
function handleTopLevelError(err: unknown): never {
|
|
25
|
+
async function handleTopLevelError(err: unknown): Promise<never> {
|
|
26
|
+
const { trackCliError, flushCliTelemetry } = await import('./telemetry')
|
|
27
|
+
trackCliError(err)
|
|
28
|
+
await flushCliTelemetry()
|
|
26
29
|
const msg = err instanceof Error ? err.message : String(err)
|
|
27
30
|
// peach's known top-level errors are all single-line, narrow strings.
|
|
28
31
|
// everything else gets the full stack so unexpected crashes still surface.
|
|
@@ -595,6 +598,11 @@ try {
|
|
|
595
598
|
break
|
|
596
599
|
}
|
|
597
600
|
|
|
601
|
+
case 'project-report': {
|
|
602
|
+
const { runProjectReport } = await import('./commands/project-report')
|
|
603
|
+
await exitWithFlush(runProjectReport(parsed.commandArgs))
|
|
604
|
+
}
|
|
605
|
+
|
|
598
606
|
case 'report-issue': {
|
|
599
607
|
const { runReportIssue } = await import('./commands/report-issue')
|
|
600
608
|
const code = await runReportIssue(parsed.commandArgs)
|
|
@@ -714,5 +722,7 @@ try {
|
|
|
714
722
|
throw error
|
|
715
723
|
}
|
|
716
724
|
|
|
725
|
+
const { flushCliTelemetry } = await import('./telemetry')
|
|
726
|
+
await flushCliTelemetry()
|
|
717
727
|
await scheduleAutomaticCliUpdate(0)
|
|
718
728
|
await scheduleAutomaticCleanup()
|
|
@@ -38,7 +38,8 @@ export const OUTBOUND_ENDPOINTS: Record<string, OutboundEndpointDeclaration> = {
|
|
|
38
38
|
category: 'bounded-diagnostics',
|
|
39
39
|
destination: 'configured PostHog ingestion host',
|
|
40
40
|
preConsent: false,
|
|
41
|
-
payload:
|
|
41
|
+
payload:
|
|
42
|
+
'anonymous install id, CLI/runtime versions, OS/arch, command name, package compatibility fields, error class and bounded path-redacted first-line error/warning messages',
|
|
42
43
|
},
|
|
43
44
|
contrast_user_action: {
|
|
44
45
|
category: 'explicit-user-action',
|
package/cli/privacy.ts
CHANGED
|
@@ -23,7 +23,6 @@ export function diagnosticsAllowedForCurrentProcess(
|
|
|
23
23
|
): boolean {
|
|
24
24
|
const doNotTrack = process.env.DO_NOT_TRACK
|
|
25
25
|
if (doNotTrack === '1' || doNotTrack === 'true') return false
|
|
26
|
-
if (!canPromptForPrivacyChoice()) return false
|
|
27
26
|
return preferences.configured && preferences.productAnalytics
|
|
28
27
|
}
|
|
29
28
|
|
|
@@ -42,7 +41,7 @@ export async function ensurePrivacyChoice(options: {
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
const allowed = await confirm(
|
|
45
|
-
'Share anonymous package incompatibilities and error
|
|
44
|
+
'Share anonymous diagnostics, including CI and agent runs: CLI/runtime versions, OS/arch, command name, random install ID, package incompatibilities, error class, and first-line error/warning messages (300 characters, paths redacted; no arguments, stacks, source or structured app data)?',
|
|
46
45
|
false,
|
|
47
46
|
)
|
|
48
47
|
// the engine's historical analytics and crash transports carry broader
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { consoleEntriesEval, type InspectBridge } from './commands/inspect/core'
|
|
2
|
+
import { diagnosticsAllowedForCurrentProcess } from './privacy'
|
|
3
|
+
import { setDiagnosticRuntimeVersion, trackCliDiagnostic } from './telemetry'
|
|
4
|
+
|
|
5
|
+
// reuse the same local console/error stores the CLI already inspects. only
|
|
6
|
+
// message strings cross into the diagnostic queue, never stacks or objects.
|
|
7
|
+
export async function collectRuntimeDiagnostics(bridge: InspectBridge): Promise<void> {
|
|
8
|
+
if (!diagnosticsAllowedForCurrentProcess()) return
|
|
9
|
+
try {
|
|
10
|
+
const result: unknown = await bridge.send(
|
|
11
|
+
{
|
|
12
|
+
type: 'evaluate',
|
|
13
|
+
code: `(() => {
|
|
14
|
+
const messages = []
|
|
15
|
+
const add = (kind, message) => {
|
|
16
|
+
if (typeof message === 'string' && message) messages.push({ kind, message: message.slice(0, 2048) })
|
|
17
|
+
}
|
|
18
|
+
const obs = window.__sootsimObservability
|
|
19
|
+
for (const e of (obs?.reportedErrors?.getSnapshot?.() || []).slice(-20)) add('runtime_error', e.message)
|
|
20
|
+
for (const [kind, entries] of [
|
|
21
|
+
['runtime_error', ${consoleEntriesEval('error', 20)}],
|
|
22
|
+
['runtime_warning', ${consoleEntriesEval('warn', 20)}],
|
|
23
|
+
]) {
|
|
24
|
+
for (const e of entries) {
|
|
25
|
+
const parts = e.args.map(a => typeof a === 'string' ? a : typeof a?.message === 'string' ? a.message : '').filter(Boolean)
|
|
26
|
+
add(kind, parts.join(' '))
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { runtimeVersion: window.__sootsimRuntimeVersion, messages: messages.slice(0, 50) }
|
|
30
|
+
})()`,
|
|
31
|
+
},
|
|
32
|
+
{ timeoutMs: 1000 },
|
|
33
|
+
)
|
|
34
|
+
if (!result || typeof result !== 'object') return
|
|
35
|
+
if ('runtimeVersion' in result && typeof result.runtimeVersion === 'string') {
|
|
36
|
+
setDiagnosticRuntimeVersion(result.runtimeVersion)
|
|
37
|
+
}
|
|
38
|
+
if (!('messages' in result) || !Array.isArray(result.messages)) return
|
|
39
|
+
for (const entry of result.messages.slice(0, 50)) {
|
|
40
|
+
if (!entry || typeof entry !== 'object') continue
|
|
41
|
+
if (
|
|
42
|
+
(entry.kind === 'runtime_error' || entry.kind === 'runtime_warning') &&
|
|
43
|
+
typeof entry.message === 'string'
|
|
44
|
+
) {
|
|
45
|
+
trackCliDiagnostic({ kind: entry.kind, message: entry.message })
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// diagnostics never delay a failed bridge beyond this bounded probe.
|
|
50
|
+
}
|
|
51
|
+
}
|
package/cli/shell-init.ts
CHANGED
|
@@ -27,7 +27,12 @@ export function renderRnxShellInit(shell: string): string {
|
|
|
27
27
|
esac
|
|
28
28
|
done
|
|
29
29
|
case "$_peach_shell_command" in
|
|
30
|
-
claim|close)
|
|
30
|
+
claim|close)
|
|
31
|
+
if [ -z "\${PEACH_CLOUD_SESSION:-}" ]; then
|
|
32
|
+
command peach "\${_peach_shell_args[@]}"
|
|
33
|
+
return $?
|
|
34
|
+
fi
|
|
35
|
+
;;
|
|
31
36
|
ios)
|
|
32
37
|
for _peach_shell_session in "$@"; do
|
|
33
38
|
if [ "$_peach_shell_session" = '--remote' ]; then
|
package/cli/telemetry.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { homedir } from 'node:os'
|
|
2
|
+
import { getCliVersion } from '../src/cli-version'
|
|
3
|
+
import { readActiveRuntime, readOrCreateAnonymousId } from '../src/home-paths'
|
|
4
|
+
import { parseArgs } from './parse-args'
|
|
1
5
|
import { diagnosticsAllowedForCurrentProcess } from './privacy'
|
|
2
6
|
|
|
3
7
|
type PackageIncompatibility = {
|
|
@@ -10,9 +14,15 @@ type PackageIncompatibility = {
|
|
|
10
14
|
type ErrorShape = {
|
|
11
15
|
kind: 'error_shape'
|
|
12
16
|
errorClass: string
|
|
17
|
+
message: string
|
|
13
18
|
}
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
type RuntimeMessage = {
|
|
21
|
+
kind: 'runtime_error' | 'runtime_warning'
|
|
22
|
+
message: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type CliDiagnostic = PackageIncompatibility | ErrorShape | RuntimeMessage
|
|
16
26
|
|
|
17
27
|
type Config = { apiKey: string; host: string }
|
|
18
28
|
|
|
@@ -32,6 +42,46 @@ function bounded(value: string, max: number): string {
|
|
|
32
42
|
return value.trim().slice(0, max)
|
|
33
43
|
}
|
|
34
44
|
|
|
45
|
+
export function sanitizeDiagnosticMessage(value: string): string {
|
|
46
|
+
const firstLine = value.slice(0, 2048).split(/[\r\n\u2028\u2029]/, 1)[0] ?? ''
|
|
47
|
+
// retain package names from dependency paths, never source filenames,
|
|
48
|
+
// URL paths/queries, local paths, stack frames, or serialized app objects.
|
|
49
|
+
return firstLine
|
|
50
|
+
.split(homedir())
|
|
51
|
+
.join('<home>')
|
|
52
|
+
.replace(
|
|
53
|
+
/<package:(?:@[\w.-]+\/)?[\w.-]+>|(?:https?:\/\/|file:\/\/|[A-Za-z]:[\\/]|<home>[\\/]|\/|\.{1,2}[\\/]|\b[\w.-]+[\\/])[^\s"'<>]+/g,
|
|
54
|
+
(location) => {
|
|
55
|
+
if (location.startsWith('<package:')) return location
|
|
56
|
+
const dependency = /(?:^|[\\/])node_modules[\\/]((?:@[^\\/]+[\\/])?[^\\/]+)/.exec(
|
|
57
|
+
location,
|
|
58
|
+
)
|
|
59
|
+
return dependency ? `<package:${dependency[1]?.replace(/\\/g, '/')}>` : '<path>'
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
.replace(/\b[\w.-]+\.(?:tsx?|jsx?|swift|m|mm|kt|java)(?::\d+(?::\d+)?)?/g, '<source>')
|
|
63
|
+
.replace(/\{.*$|\[(?:\s*["'{\d]|$).*$/g, '<data>')
|
|
64
|
+
.replace(
|
|
65
|
+
/\b(?:const|let|var)\s+\w+\s*=.*$|\b(?:function|class)\s+\w+.*$|\b(?:import|export)\s+.*$|\breturn\s*[({<].*$/g,
|
|
66
|
+
'<source>',
|
|
67
|
+
)
|
|
68
|
+
.replace(/^\s*at\s+.*$|\bat\s+[\w.$<>]+\s*\([^)]*\)/g, '<stack>')
|
|
69
|
+
.trim()
|
|
70
|
+
.slice(0, 300)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let observedRuntimeVersion: string | null = null
|
|
74
|
+
|
|
75
|
+
function diagnosticVersion(version: string | null): string | null {
|
|
76
|
+
return version && version.length <= 80 && /^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(version)
|
|
77
|
+
? version
|
|
78
|
+
: null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function setDiagnosticRuntimeVersion(version: string): void {
|
|
82
|
+
observedRuntimeVersion = diagnosticVersion(version)
|
|
83
|
+
}
|
|
84
|
+
|
|
35
85
|
export function buildCliDiagnosticEvent(diagnostic: CliDiagnostic) {
|
|
36
86
|
const properties =
|
|
37
87
|
diagnostic.kind === 'package_incompatibility'
|
|
@@ -42,12 +92,24 @@ export function buildCliDiagnosticEvent(diagnostic: CliDiagnostic) {
|
|
|
42
92
|
? { unsupported_api: bounded(diagnostic.unsupportedApi, 160) }
|
|
43
93
|
: {}),
|
|
44
94
|
}
|
|
45
|
-
:
|
|
95
|
+
: diagnostic.kind === 'error_shape'
|
|
96
|
+
? {
|
|
97
|
+
error_class: bounded(diagnostic.errorClass, 160),
|
|
98
|
+
message: sanitizeDiagnosticMessage(diagnostic.message),
|
|
99
|
+
}
|
|
100
|
+
: { message: sanitizeDiagnosticMessage(diagnostic.message) }
|
|
46
101
|
|
|
47
102
|
return {
|
|
48
103
|
event: diagnostic.kind,
|
|
49
|
-
distinct_id:
|
|
50
|
-
properties
|
|
104
|
+
distinct_id: readOrCreateAnonymousId(),
|
|
105
|
+
properties: {
|
|
106
|
+
cli_version: diagnosticVersion(getCliVersion()),
|
|
107
|
+
runtime_version: observedRuntimeVersion ?? diagnosticVersion(readActiveRuntime()),
|
|
108
|
+
os: process.platform,
|
|
109
|
+
arch: process.arch,
|
|
110
|
+
command: parseArgs(process.argv).command ?? 'open',
|
|
111
|
+
...properties,
|
|
112
|
+
},
|
|
51
113
|
}
|
|
52
114
|
}
|
|
53
115
|
|
|
@@ -55,12 +117,20 @@ const queue: CliDiagnostic[] = []
|
|
|
55
117
|
|
|
56
118
|
export function trackCliDiagnostic(diagnostic: CliDiagnostic): void {
|
|
57
119
|
if (!diagnosticsAllowedForCurrentProcess()) return
|
|
58
|
-
queue.
|
|
120
|
+
if (queue.length >= 50) return
|
|
121
|
+
const entry =
|
|
122
|
+
diagnostic.kind === 'package_incompatibility'
|
|
123
|
+
? diagnostic
|
|
124
|
+
: { ...diagnostic, message: sanitizeDiagnosticMessage(diagnostic.message) }
|
|
125
|
+
if (queue.some((queued) => JSON.stringify(queued) === JSON.stringify(entry))) return
|
|
126
|
+
queue.push(entry)
|
|
59
127
|
}
|
|
60
128
|
|
|
61
129
|
export function trackCliError(error: unknown): void {
|
|
62
130
|
const errorClass = error instanceof Error ? error.constructor.name : typeof error
|
|
63
|
-
|
|
131
|
+
const message =
|
|
132
|
+
error instanceof Error ? error.message : typeof error === 'string' ? error : ''
|
|
133
|
+
trackCliDiagnostic({ kind: 'error_shape', errorClass, message })
|
|
64
134
|
}
|
|
65
135
|
|
|
66
136
|
export async function flushCliTelemetry(): Promise<void> {
|
|
@@ -70,8 +140,9 @@ export async function flushCliTelemetry(): Promise<void> {
|
|
|
70
140
|
queue.length = 0
|
|
71
141
|
return
|
|
72
142
|
}
|
|
73
|
-
const
|
|
143
|
+
const diagnostics = queue.splice(0, queue.length)
|
|
74
144
|
try {
|
|
145
|
+
const batch = diagnostics.map((diagnostic) => buildCliDiagnosticEvent(diagnostic))
|
|
75
146
|
await fetch(`${config.host}/batch/`, {
|
|
76
147
|
method: 'POST',
|
|
77
148
|
headers: { 'content-type': 'application/json' },
|
package/cli/ws-bridge.ts
CHANGED
|
@@ -148,7 +148,8 @@ function resolveDefaultDevBridgeLockfile(): DevBridgeLockfile | null {
|
|
|
148
148
|
// another checkout's bridge on its own offset belongs to that checkout's
|
|
149
149
|
// world; only the canonical bridge is shared, and past it the daemon serves.
|
|
150
150
|
return (
|
|
151
|
-
lockfiles.find((lockfile) => lockfile.bridgePort === DEFAULT_SOOTSIM_BRIDGE_PORT) ??
|
|
151
|
+
lockfiles.find((lockfile) => lockfile.bridgePort === DEFAULT_SOOTSIM_BRIDGE_PORT) ??
|
|
152
|
+
null
|
|
152
153
|
)
|
|
153
154
|
}
|
|
154
155
|
|
|
@@ -601,7 +602,12 @@ export function createBridge(wsPort: number, opts: WsBridgeOptions = {}): WsBrid
|
|
|
601
602
|
// err.message is often empty (ECONNREFUSED surfaces with no message on
|
|
602
603
|
// some node builds) — don't emit a dangling "...7668: " with no cause.
|
|
603
604
|
const cause = err.message ? `: ${err.message}` : ''
|
|
604
|
-
reject(
|
|
605
|
+
reject(
|
|
606
|
+
new Error(
|
|
607
|
+
`could not connect to ${bridgeWsUrl(wsPort)}${cause}\n` +
|
|
608
|
+
` for sandboxed or agent runners, keep \`peach serve --port ${wsPort}\` running in a persistent terminal or background task.`,
|
|
609
|
+
),
|
|
610
|
+
)
|
|
605
611
|
})
|
|
606
612
|
})
|
|
607
613
|
// a bridge closed before its handshake finished rejects `ready` with nothing
|
package/detox/index.ts
CHANGED
|
@@ -605,7 +605,7 @@ export async function waitForSootsimIdle(
|
|
|
605
605
|
// find a node in the sootsim tree matching a matcher descriptor
|
|
606
606
|
async function findNodeByMatcher(matcher: Matcher): Promise<any> {
|
|
607
607
|
const page = getPage()
|
|
608
|
-
if (matcher.index !== undefined) {
|
|
608
|
+
if (matcher.index !== undefined || matcher.type === 'text') {
|
|
609
609
|
return page.evaluate(async (indexedMatcher: Matcher) => {
|
|
610
610
|
const filter =
|
|
611
611
|
indexedMatcher.type === 'id'
|
|
@@ -618,8 +618,8 @@ async function findNodeByMatcher(matcher: Matcher): Promise<any> {
|
|
|
618
618
|
? { hasRole: indexedMatcher.value }
|
|
619
619
|
: { type: indexedMatcher.value }
|
|
620
620
|
const results = await window.__sootsimTest!.queryAll(filter)
|
|
621
|
-
// detox
|
|
622
|
-
//
|
|
621
|
+
// detox by.text matches exact text elements, with or without an index.
|
|
622
|
+
// hasText also matches ancestors and substrings, which are not targets.
|
|
623
623
|
const indexed =
|
|
624
624
|
indexedMatcher.type === 'text'
|
|
625
625
|
? results.filter(
|
|
@@ -634,11 +634,6 @@ async function findNodeByMatcher(matcher: Matcher): Promise<any> {
|
|
|
634
634
|
(id: string) => window.__sootsimTest!.findByTestId(id),
|
|
635
635
|
matcher.value,
|
|
636
636
|
)
|
|
637
|
-
} else if (matcher.type === 'text') {
|
|
638
|
-
return page.evaluate(
|
|
639
|
-
(text: string) => window.__sootsimTest!.findByText(text),
|
|
640
|
-
matcher.value,
|
|
641
|
-
)
|
|
642
637
|
} else if (matcher.type === 'label') {
|
|
643
638
|
return page.evaluate(
|
|
644
639
|
(label: string) => window.__sootsimTest!.findByLabel(label),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.632 | (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.632 | (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.632 | (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.632 | (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.632 | (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.632 | (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.632 | (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.632 | (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.632 | (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.632 | (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.632 | (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.632 | (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.632 | (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.632 | (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.632 | (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;
|
|
@@ -39,7 +39,9 @@ var CLOUD_TEST_VALUE_FLAGS = [
|
|
|
39
39
|
"--testPathPattern",
|
|
40
40
|
"--include-tags",
|
|
41
41
|
"--exclude-tags",
|
|
42
|
-
"--grep"
|
|
42
|
+
"--grep",
|
|
43
|
+
"--env",
|
|
44
|
+
"-e"
|
|
43
45
|
];
|
|
44
46
|
var CLOUD_TEST_VALUE_FLAG_SET = new Set(CLOUD_TEST_VALUE_FLAGS);
|
|
45
47
|
function isCloudTestValueFlag(name) {
|
|
@@ -64,7 +66,7 @@ function parseCloudTestArgs(value) {
|
|
|
64
66
|
return { ok: false, message: "testArgs entries must be non-empty strings" };
|
|
65
67
|
}
|
|
66
68
|
if (entry.length > CLOUD_TEST_ARG_MAX_LENGTH || entry.includes("\0")) {
|
|
67
|
-
return { ok: false, message:
|
|
69
|
+
return { ok: false, message: "testArgs entry is too long or carries a NUL byte" };
|
|
68
70
|
}
|
|
69
71
|
bytes += encoder.encode(entry).length;
|
|
70
72
|
if (bytes > CLOUD_TEST_ARGS_MAX_BYTES) {
|
|
@@ -87,12 +89,19 @@ function parseCloudTestArgs(value) {
|
|
|
87
89
|
const eq = entry.indexOf("=");
|
|
88
90
|
const head = eq > 0 ? entry.slice(0, eq) : entry;
|
|
89
91
|
if (!isCloudTestValueFlag(head)) {
|
|
90
|
-
return {
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
message: `testArgs carries an unknown flag: ${head.slice(0, 60)}`
|
|
95
|
+
};
|
|
91
96
|
}
|
|
92
97
|
if (eq > 0) {
|
|
93
|
-
|
|
98
|
+
const value2 = entry.slice(eq + 1);
|
|
99
|
+
if (!value2) {
|
|
94
100
|
return { ok: false, message: `${head} expects a value` };
|
|
95
101
|
}
|
|
102
|
+
if ((head === "--env" || head === "-e") && value2.indexOf("=") < 1) {
|
|
103
|
+
return { ok: false, message: `${head} expects KEY=VALUE with a non-empty key` };
|
|
104
|
+
}
|
|
96
105
|
args.push(entry);
|
|
97
106
|
continue;
|
|
98
107
|
}
|
|
@@ -100,6 +109,9 @@ function parseCloudTestArgs(value) {
|
|
|
100
109
|
if (val === void 0 || val.startsWith("-")) {
|
|
101
110
|
return { ok: false, message: `${head} expects a value` };
|
|
102
111
|
}
|
|
112
|
+
if ((head === "--env" || head === "-e") && val.indexOf("=") < 1) {
|
|
113
|
+
return { ok: false, message: `${head} expects KEY=VALUE with a non-empty key` };
|
|
114
|
+
}
|
|
103
115
|
args.push(entry, val);
|
|
104
116
|
i += 1;
|
|
105
117
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.632 | (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;
|
|
@@ -10,7 +10,9 @@ var CLOUD_TEST_VALUE_FLAGS = [
|
|
|
10
10
|
"--testPathPattern",
|
|
11
11
|
"--include-tags",
|
|
12
12
|
"--exclude-tags",
|
|
13
|
-
"--grep"
|
|
13
|
+
"--grep",
|
|
14
|
+
"--env",
|
|
15
|
+
"-e"
|
|
14
16
|
];
|
|
15
17
|
var CLOUD_TEST_VALUE_FLAG_SET = new Set(CLOUD_TEST_VALUE_FLAGS);
|
|
16
18
|
function isCloudTestValueFlag(name) {
|
|
@@ -35,7 +37,7 @@ function parseCloudTestArgs(value) {
|
|
|
35
37
|
return { ok: false, message: "testArgs entries must be non-empty strings" };
|
|
36
38
|
}
|
|
37
39
|
if (entry.length > CLOUD_TEST_ARG_MAX_LENGTH || entry.includes("\0")) {
|
|
38
|
-
return { ok: false, message:
|
|
40
|
+
return { ok: false, message: "testArgs entry is too long or carries a NUL byte" };
|
|
39
41
|
}
|
|
40
42
|
bytes += encoder.encode(entry).length;
|
|
41
43
|
if (bytes > CLOUD_TEST_ARGS_MAX_BYTES) {
|
|
@@ -58,12 +60,19 @@ function parseCloudTestArgs(value) {
|
|
|
58
60
|
const eq = entry.indexOf("=");
|
|
59
61
|
const head = eq > 0 ? entry.slice(0, eq) : entry;
|
|
60
62
|
if (!isCloudTestValueFlag(head)) {
|
|
61
|
-
return {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
message: `testArgs carries an unknown flag: ${head.slice(0, 60)}`
|
|
66
|
+
};
|
|
62
67
|
}
|
|
63
68
|
if (eq > 0) {
|
|
64
|
-
|
|
69
|
+
const value2 = entry.slice(eq + 1);
|
|
70
|
+
if (!value2) {
|
|
65
71
|
return { ok: false, message: `${head} expects a value` };
|
|
66
72
|
}
|
|
73
|
+
if ((head === "--env" || head === "-e") && value2.indexOf("=") < 1) {
|
|
74
|
+
return { ok: false, message: `${head} expects KEY=VALUE with a non-empty key` };
|
|
75
|
+
}
|
|
67
76
|
args.push(entry);
|
|
68
77
|
continue;
|
|
69
78
|
}
|
|
@@ -71,6 +80,9 @@ function parseCloudTestArgs(value) {
|
|
|
71
80
|
if (val === void 0 || val.startsWith("-")) {
|
|
72
81
|
return { ok: false, message: `${head} expects a value` };
|
|
73
82
|
}
|
|
83
|
+
if ((head === "--env" || head === "-e") && val.indexOf("=") < 1) {
|
|
84
|
+
return { ok: false, message: `${head} expects KEY=VALUE with a non-empty key` };
|
|
85
|
+
}
|
|
74
86
|
args.push(entry, val);
|
|
75
87
|
i += 1;
|
|
76
88
|
}
|