peachsim 0.1.527
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/LICENSE +22 -0
- package/README.md +640 -0
- package/cli/app-config.ts +82 -0
- package/cli/app-fonts.ts +388 -0
- package/cli/app-project.ts +59 -0
- package/cli/app-splash.ts +171 -0
- package/cli/app-state-reset.ts +26 -0
- package/cli/auth.ts +184 -0
- package/cli/bin.ts +59 -0
- package/cli/bridge-diagnostics.ts +226 -0
- package/cli/bridge-flow-runner.ts +2806 -0
- package/cli/browser-backend.ts +44 -0
- package/cli/browser-evals.ts +96 -0
- package/cli/cli-update.ts +261 -0
- package/cli/cloud-client.ts +1530 -0
- package/cli/cloud-dispatch.ts +206 -0
- package/cli/cloud-session.ts +227 -0
- package/cli/command-contract.ts +221 -0
- package/cli/command-registry.ts +304 -0
- package/cli/commands/agent-wrapper.ts +1048 -0
- package/cli/commands/agent.ts +439 -0
- package/cli/commands/app-fonts.ts +99 -0
- package/cli/commands/assert.ts +542 -0
- package/cli/commands/auth.ts +71 -0
- package/cli/commands/box/bundle-plane.ts +71 -0
- package/cli/commands/box/checkout-plane.ts +256 -0
- package/cli/commands/box/plane-share.ts +208 -0
- package/cli/commands/box/rnx-command.ts +60 -0
- package/cli/commands/box/search-command.ts +157 -0
- package/cli/commands/box.ts +925 -0
- package/cli/commands/camera.ts +266 -0
- package/cli/commands/cleanup.ts +169 -0
- package/cli/commands/compat.ts +176 -0
- package/cli/commands/config.ts +34 -0
- package/cli/commands/control.ts +2154 -0
- package/cli/commands/cpu-profile.ts +941 -0
- package/cli/commands/daemon-mac-app.ts +165 -0
- package/cli/commands/daemon.ts +800 -0
- package/cli/commands/debug.ts +751 -0
- package/cli/commands/desktop.ts +39 -0
- package/cli/commands/detect.ts +201 -0
- package/cli/commands/detox.ts +481 -0
- package/cli/commands/device.ts +134 -0
- package/cli/commands/diagnose.ts +589 -0
- package/cli/commands/do-chain.ts +408 -0
- package/cli/commands/electron.ts +94 -0
- package/cli/commands/film.ts +379 -0
- package/cli/commands/flow.ts +1240 -0
- package/cli/commands/inspect/actions.ts +516 -0
- package/cli/commands/inspect/core.ts +2086 -0
- package/cli/commands/inspect/count.ts +17 -0
- package/cli/commands/inspect/describe.ts +119 -0
- package/cli/commands/inspect/env.ts +25 -0
- package/cli/commands/inspect/find.ts +169 -0
- package/cli/commands/inspect/get-layout.ts +53 -0
- package/cli/commands/inspect/keyboard.ts +54 -0
- package/cli/commands/inspect/list.ts +175 -0
- package/cli/commands/inspect/memory.ts +215 -0
- package/cli/commands/inspect/redaction.ts +39 -0
- package/cli/commands/inspect/resolve-target.ts +77 -0
- package/cli/commands/inspect/screens.ts +78 -0
- package/cli/commands/inspect/settle.ts +32 -0
- package/cli/commands/inspect/settling.ts +197 -0
- package/cli/commands/inspect/shared.ts +259 -0
- package/cli/commands/inspect/sleep.ts +15 -0
- package/cli/commands/inspect/tree.ts +23 -0
- package/cli/commands/inspect/url.ts +17 -0
- package/cli/commands/inspect/wait-event.ts +211 -0
- package/cli/commands/inspect/wait-idle.ts +25 -0
- package/cli/commands/inspect/wait-ready.ts +87 -0
- package/cli/commands/inspect/wait-selector.ts +59 -0
- package/cli/commands/inspect/wait.ts +32 -0
- package/cli/commands/inspect.ts +4738 -0
- package/cli/commands/install-cli.ts +275 -0
- package/cli/commands/install-desktop.ts +385 -0
- package/cli/commands/login.ts +357 -0
- package/cli/commands/logout.ts +24 -0
- package/cli/commands/maestro-generate.ts +375 -0
- package/cli/commands/maestro.ts +536 -0
- package/cli/commands/mode.ts +58 -0
- package/cli/commands/no-bridge-hint.ts +80 -0
- package/cli/commands/perf.ts +67 -0
- package/cli/commands/permissions.ts +203 -0
- package/cli/commands/platform.ts +410 -0
- package/cli/commands/preview.ts +103 -0
- package/cli/commands/profile.ts +106 -0
- package/cli/commands/react.ts +353 -0
- package/cli/commands/record.ts +1435 -0
- package/cli/commands/report-issue.ts +305 -0
- package/cli/commands/reset.ts +89 -0
- package/cli/commands/runtime.ts +348 -0
- package/cli/commands/screenshot-command.ts +106 -0
- package/cli/commands/screenshot-layers.ts +144 -0
- package/cli/commands/screenshot-mode.ts +38 -0
- package/cli/commands/screenshot.ts +492 -0
- package/cli/commands/screenshots-capture.ts +608 -0
- package/cli/commands/screenshots.ts +127 -0
- package/cli/commands/serve.ts +139 -0
- package/cli/commands/setup.ts +416 -0
- package/cli/commands/shell-boolean-mode.ts +82 -0
- package/cli/commands/skills.ts +468 -0
- package/cli/commands/slides.ts +357 -0
- package/cli/commands/state.ts +94 -0
- package/cli/commands/storage.ts +58 -0
- package/cli/commands/telemetry.ts +55 -0
- package/cli/commands/test.ts +535 -0
- package/cli/commands/three-mode.ts +771 -0
- package/cli/commands/timeline.ts +121 -0
- package/cli/commands/upgrade.ts +167 -0
- package/cli/commands/upload.ts +1203 -0
- package/cli/commands/version.ts +63 -0
- package/cli/commands/welcome.ts +17 -0
- package/cli/commands/what-happened.ts +328 -0
- package/cli/current-sim.ts +217 -0
- package/cli/desktop-companion.ts +315 -0
- package/cli/drivers/electron.ts +70 -0
- package/cli/drivers/index.ts +20 -0
- package/cli/drivers/playwright-provisioning.ts +184 -0
- package/cli/drivers/playwright-sim-host.ts +537 -0
- package/cli/drivers/playwright.ts +209 -0
- package/cli/drivers/registry.ts +65 -0
- package/cli/drivers/types.ts +102 -0
- package/cli/flow-export.ts +55 -0
- package/cli/flow-file.ts +142 -0
- package/cli/flow-live-status.ts +126 -0
- package/cli/flow-session.ts +140 -0
- package/cli/help-core.ts +143 -0
- package/cli/help.ts +44 -0
- package/cli/hidden-runtime-alias.ts +19 -0
- package/cli/hints.ts +214 -0
- package/cli/inspect-notice-state.ts +114 -0
- package/cli/internal-child.ts +176 -0
- package/cli/maestro-js.ts +323 -0
- package/cli/main.ts +704 -0
- package/cli/open-url.ts +8 -0
- package/cli/outbound-endpoints.ts +830 -0
- package/cli/parent-pid.ts +204 -0
- package/cli/parse-args.ts +189 -0
- package/cli/privacy.ts +53 -0
- package/cli/prompt.ts +56 -0
- package/cli/recording-access.ts +108 -0
- package/cli/registry.ts +2 -0
- package/cli/resolve-assets.ts +63 -0
- package/cli/rnx-in-process.ts +98 -0
- package/cli/run-registry.ts +226 -0
- package/cli/run-rnx-core.ts +148 -0
- package/cli/run-rnx.ts +137 -0
- package/cli/runtime-notes.ts +66 -0
- package/cli/runtime-summary.ts +25 -0
- package/cli/secure-file.ts +31 -0
- package/cli/self-invocation.ts +58 -0
- package/cli/send-to-box.ts +109 -0
- package/cli/setup-repository.ts +191 -0
- package/cli/shell-init.ts +98 -0
- package/cli/standalone.ts +4 -0
- package/cli/telemetry.ts +84 -0
- package/cli/ui.ts +68 -0
- package/cli/ws-bridge.ts +1020 -0
- package/detox/attempt-events.cjs +23 -0
- package/detox/colors.ts +54 -0
- package/detox/config-loader.ts +135 -0
- package/detox/element-types.ts +48 -0
- package/detox/expectations.ts +534 -0
- package/detox/gestures.ts +501 -0
- package/detox/index.ts +2694 -0
- package/detox/jest-preset.cjs +29 -0
- package/detox/jest-setup-after-env.cjs +22 -0
- package/detox/matchers.ts +30 -0
- package/detox/motion-change.cjs +109 -0
- package/detox/motion-change.d.cts +35 -0
- package/detox/navigation-lifecycle.ts +290 -0
- package/detox/navigation.ts +43 -0
- package/detox/proof-frame.cjs +114 -0
- package/detox/record-block-execution.cjs +78 -0
- package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
- package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
- package/detox/screenshots/color-test-rest-norngh.png +0 -0
- package/detox/screenshots/theme-blue-toggle.png +0 -0
- package/detox/screenshots/theme-blue.png +0 -0
- package/detox/screenshots/theme-red-toggle.png +0 -0
- package/detox/screenshots/theme-red.png +0 -0
- package/dist-lib/agent-daemon-client.cjs +502 -0
- package/dist-lib/agent-events.cjs +48 -0
- package/dist-lib/agent-identity.cjs +53 -0
- package/dist-lib/agent-sessions.cjs +759 -0
- package/dist-lib/attached-projects.cjs +526 -0
- package/dist-lib/auth/shared-session.cjs +208 -0
- package/dist-lib/backend-origin.cjs +88 -0
- package/dist-lib/beta.cjs +41 -0
- package/dist-lib/beta.mjs +13 -0
- package/dist-lib/bridge-constants.cjs +161 -0
- package/dist-lib/bridge-contract-input.cjs +451 -0
- package/dist-lib/bridge-contract-input.mjs +414 -0
- package/dist-lib/bridge-contract.cjs +130 -0
- package/dist-lib/bridge-contract.mjs +94 -0
- package/dist-lib/capture-contract.cjs +220 -0
- package/dist-lib/capture-contract.mjs +190 -0
- package/dist-lib/cli-constants.cjs +32 -0
- package/dist-lib/cloud-contract.cjs +194 -0
- package/dist-lib/cloud-contract.mjs +145 -0
- package/dist-lib/cloud.cjs +670 -0
- package/dist-lib/cloud.mjs +639 -0
- package/dist-lib/config.cjs +96 -0
- package/dist-lib/detox/index.cjs +3789 -0
- package/dist-lib/detox/jest-preset.cjs +29 -0
- package/dist-lib/dev-bundle-resolution.cjs +290 -0
- package/dist-lib/home-paths.cjs +638 -0
- package/dist-lib/host/bridge-host.cjs +10903 -0
- package/dist-lib/host/fetch-proxy-handler.cjs +421 -0
- package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
- package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
- package/dist-lib/host/replacement-module-handler.cjs +469 -0
- package/dist-lib/host/websocket-proxy.cjs +298 -0
- package/dist-lib/index.cjs +4268 -0
- package/dist-lib/jump-to-source-babel.cjs +93 -0
- package/dist-lib/jump-to-source-native.cjs +112 -0
- package/dist-lib/menu.cjs +646 -0
- package/dist-lib/menu.mjs +604 -0
- package/dist-lib/metro-fingerprint-registry.cjs +186 -0
- package/dist-lib/metro-fingerprint-registry.mjs +156 -0
- package/dist-lib/metro-production-bundle.cjs +329 -0
- package/dist-lib/metro-production-bundle.mjs +295 -0
- package/dist-lib/metro.cjs +904 -0
- package/dist-lib/profiles.cjs +246 -0
- package/dist-lib/public-brand.cjs +39 -0
- package/dist-lib/react-native-host-modules.cjs +51 -0
- package/dist-lib/react-native-host-modules.mjs +25 -0
- package/dist-lib/render-mode.cjs +54 -0
- package/dist-lib/scripts/dev-server-scanner.cjs +1232 -0
- package/dist-lib/sdk.cjs +2433 -0
- package/dist-lib/sdk.mjs +2336 -0
- package/dist-lib/skills.cjs +39132 -0
- package/dist-lib/swift-submit.cjs +92 -0
- package/dist-lib/swift-submit.mjs +65 -0
- package/dist-lib/swift.cjs +1646 -0
- package/dist-lib/vite.cjs +1591 -0
- package/package.json +290 -0
- package/scripts/dev-server-scanner.ts +1291 -0
- package/scripts/optional-demo-registry.ts +113 -0
- package/skills/contrast/SKILL.md +165 -0
- package/skills/rnx-debug/SKILL.md +433 -0
- package/skills/rnx-perf/SKILL.md +399 -0
- package/skills/rnx-setup/SKILL.md +172 -0
- package/skills/rnx-test/SKILL.md +321 -0
- package/skills/rnx-visual/SKILL.md +126 -0
- package/src/agent-daemon-client.ts +410 -0
- package/src/agent-events.ts +71 -0
- package/src/agent-identity.ts +23 -0
- package/src/agent-prompt.ts +71 -0
- package/src/agent-sessions.ts +580 -0
- package/src/app-url.ts +39 -0
- package/src/attached-projects.ts +539 -0
- package/src/auth/shared-session.ts +235 -0
- package/src/backend-origin.ts +70 -0
- package/src/beta.ts +19 -0
- package/src/bridge-constants.ts +175 -0
- package/src/bridge-contract-input.ts +502 -0
- package/src/bridge-contract.ts +558 -0
- package/src/browser-cache.ts +16 -0
- package/src/capture-contract.ts +587 -0
- package/src/cli-constants.ts +1 -0
- package/src/cli-version.ts +40 -0
- package/src/cloud-contract.ts +274 -0
- package/src/cloud.ts +762 -0
- package/src/codex-client.ts +234 -0
- package/src/config.ts +132 -0
- package/src/connect.ts +101 -0
- package/src/dev-bundle-resolution.ts +346 -0
- package/src/dev-server-open.ts +198 -0
- package/src/disk-cleanup.ts +540 -0
- package/src/flow-timeout-scale.ts +15 -0
- package/src/home-paths.ts +806 -0
- package/src/host/agent-host.ts +666 -0
- package/src/host/bridge-host.ts +2788 -0
- package/src/host/fetch-proxy-handler.ts +449 -0
- package/src/host/fetch-proxy-overrides.ts +51 -0
- package/src/host/open-url.ts +89 -0
- package/src/host/plane-host.ts +203 -0
- package/src/host/replacement-module-handler.ts +158 -0
- package/src/host/websocket-proxy.ts +315 -0
- package/src/index.ts +71 -0
- package/src/jump-to-source-babel.ts +135 -0
- package/src/jump-to-source-native.ts +102 -0
- package/src/menu.ts +777 -0
- package/src/metro-fingerprint-registry.ts +302 -0
- package/src/metro-plugin.ts +341 -0
- package/src/metro-production-bundle.ts +471 -0
- package/src/native-dev-bundle-url.ts +118 -0
- package/src/native-seam-manifest.ts +341 -0
- package/src/playwright-host-log.ts +16 -0
- package/src/plist.ts +27 -0
- package/src/poll-until.ts +123 -0
- package/src/profiles.ts +179 -0
- package/src/public-brand.ts +9 -0
- package/src/react-native-host-modules.ts +26 -0
- package/src/render-mode.ts +27 -0
- package/src/rolldown-plugin-swift.ts +240 -0
- package/src/runtime-assets.ts +233 -0
- package/src/runtime-delivery.ts +54 -0
- package/src/screenshots/compose.ts +422 -0
- package/src/screenshots/frame-compose.ts +514 -0
- package/src/screenshots/orchestrate.ts +256 -0
- package/src/screenshots/org-deck.ts +91 -0
- package/src/screenshots/registry.ts +58 -0
- package/src/screenshots/schema.ts +364 -0
- package/src/sdk.ts +15 -0
- package/src/settings-bundle.ts +418 -0
- package/src/shell-bridge-identity.ts +15 -0
- package/src/sim-client.ts +697 -0
- package/src/skills/builtin/a11y-review.ts +132 -0
- package/src/skills/builtin/compat-check.ts +71 -0
- package/src/skills/builtin/maestro-test.ts +121 -0
- package/src/skills/builtin/perf-profile.ts +90 -0
- package/src/skills/builtin/screenshot-all.ts +52 -0
- package/src/skills/builtin/visual-diff.ts +100 -0
- package/src/skills/registry.ts +107 -0
- package/src/skills/types.ts +41 -0
- package/src/swift-submit.ts +185 -0
- package/src/swift.ts +7 -0
- package/src/team-machine-prompt.ts +73 -0
- package/src/vite-plugin-one.ts +264 -0
- package/src/vite-plugin-swift.ts +620 -0
- package/src/vite-plugin.ts +1946 -0
- package/src/worklets-babel.ts +250 -0
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
// rnx get diagnosis — composed read commands for "what just went wrong?"
|
|
2
|
+
//
|
|
3
|
+
// the demo-friendly compact diagnostic surface called out in
|
|
4
|
+
// plans/expensify-sootsim-agent-device-demo.md. one command, one round
|
|
5
|
+
// of bridge calls, one paste-able output that beats the agent-device
|
|
6
|
+
// sed/grep workflow in both speed and information density.
|
|
7
|
+
//
|
|
8
|
+
// usage:
|
|
9
|
+
// rnx get diagnosis [--since 90s] [--include react,network,console,screen,fetch,alert]
|
|
10
|
+
// rnx get diagnosis --json
|
|
11
|
+
//
|
|
12
|
+
// the output is grouped by source (timeline, network, react, console)
|
|
13
|
+
// rather than chronological. for chronological order use
|
|
14
|
+
// `rnx what-happened` directly. `recent` is the only subcommand
|
|
15
|
+
// today; `diagnose <something-else>` may be added later (e.g. boot,
|
|
16
|
+
// scrolling-jank, idle-burn).
|
|
17
|
+
//
|
|
18
|
+
// design note: every dimension here is a single bridge call against an
|
|
19
|
+
// existing primitive. this command exists to remove the agent's tax of
|
|
20
|
+
// chaining commands and reformatting four output styles, not to add a
|
|
21
|
+
// new data source. when a new data source becomes worth surfacing in a
|
|
22
|
+
// "diagnose" pass, add it as one more parallel call here.
|
|
23
|
+
|
|
24
|
+
import { getCliIdentityKey } from '../current-sim'
|
|
25
|
+
import {
|
|
26
|
+
callInBridge,
|
|
27
|
+
createBridgeFromParsed,
|
|
28
|
+
parseBridgeCliArgs,
|
|
29
|
+
type WsBridge,
|
|
30
|
+
} from '../ws-bridge'
|
|
31
|
+
import type {
|
|
32
|
+
SootSimTimelineEvent,
|
|
33
|
+
SootSimTimelineQuery,
|
|
34
|
+
SootSimTimelineQueryResult,
|
|
35
|
+
} from '@rnx/globals'
|
|
36
|
+
|
|
37
|
+
interface DiagnoseOptions {
|
|
38
|
+
port?: number
|
|
39
|
+
verbose?: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface NetworkEntry {
|
|
43
|
+
id: string
|
|
44
|
+
url: string
|
|
45
|
+
displayUrl?: string
|
|
46
|
+
method?: string
|
|
47
|
+
status?: number | null
|
|
48
|
+
durationMs?: number | null
|
|
49
|
+
error?: string | null
|
|
50
|
+
// observability store uses startTs (epoch ms) as the entry's start
|
|
51
|
+
// time. accept either name for forward-compat with any future
|
|
52
|
+
// rename.
|
|
53
|
+
startTs?: number
|
|
54
|
+
startedAt?: number
|
|
55
|
+
responseAt?: number | null
|
|
56
|
+
responseSize?: number | null
|
|
57
|
+
size?: number | null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface SlowRow {
|
|
61
|
+
displayName: string
|
|
62
|
+
totalMs: number
|
|
63
|
+
avgMs: number
|
|
64
|
+
commitCount: number
|
|
65
|
+
slowestCommit: number
|
|
66
|
+
slowestMs: number
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface RerenderRow {
|
|
70
|
+
displayName: string
|
|
71
|
+
count?: number
|
|
72
|
+
renders?: number
|
|
73
|
+
causes?: Record<string, number>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface LogEntry {
|
|
77
|
+
level: string
|
|
78
|
+
// observability ring uses `ts` (epoch ms) and `args` (string[]).
|
|
79
|
+
// older shapes had `t` + `message`. accept both.
|
|
80
|
+
ts?: number
|
|
81
|
+
t?: number
|
|
82
|
+
args?: unknown[]
|
|
83
|
+
message?: string
|
|
84
|
+
source?: string
|
|
85
|
+
context?: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function logTs(e: LogEntry): number {
|
|
89
|
+
return e.ts ?? e.t ?? 0
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function logMessage(e: LogEntry): string {
|
|
93
|
+
if (typeof e.message === 'string' && e.message.length > 0) return e.message
|
|
94
|
+
if (Array.isArray(e.args)) {
|
|
95
|
+
return e.args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ')
|
|
96
|
+
}
|
|
97
|
+
return ''
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const DEFAULT_INCLUDES = new Set(['timeline', 'network', 'react', 'console', 'screen'])
|
|
101
|
+
|
|
102
|
+
function printHelp(): void {
|
|
103
|
+
console.log(`
|
|
104
|
+
rnx get diagnosis — compact "what just went wrong?" report
|
|
105
|
+
|
|
106
|
+
usage:
|
|
107
|
+
rnx get diagnosis [--since <duration>] [--include <csv>] [--json]
|
|
108
|
+
|
|
109
|
+
options:
|
|
110
|
+
--since <d> absolute window for timeline + network (e.g. 90s, 2m, 500ms; default 90s)
|
|
111
|
+
--include <csv> comma-separated dimensions: timeline, network, react, console, screen
|
|
112
|
+
(default: all)
|
|
113
|
+
--slow-threshold <n> network slow-request threshold in ms (default 1000)
|
|
114
|
+
--limit <n> rows to print per react table (default 8)
|
|
115
|
+
--json emit a single structured JSON document
|
|
116
|
+
|
|
117
|
+
the diagnose report runs every dimension in parallel against the same
|
|
118
|
+
sim and returns a grouped summary. use \`rnx what-happened\`
|
|
119
|
+
for chronological events, \`rnx network\` for full request inspection,
|
|
120
|
+
\`rnx perf react summary\` for the React tables on their own.
|
|
121
|
+
|
|
122
|
+
examples:
|
|
123
|
+
rnx get diagnosis
|
|
124
|
+
rnx get diagnosis --since 30s --include network,react
|
|
125
|
+
rnx get diagnosis --json > /tmp/diagnosis.json
|
|
126
|
+
`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function parseSinceMs(value: string | undefined, fallback: number): number {
|
|
130
|
+
if (!value) return fallback
|
|
131
|
+
const m = /^(\d+(?:\.\d+)?)(ms|s|m)?$/.exec(value.trim())
|
|
132
|
+
if (!m) return fallback
|
|
133
|
+
const n = Number(m[1])
|
|
134
|
+
const unit = m[2] ?? 'ms'
|
|
135
|
+
return unit === 's' ? n * 1000 : unit === 'm' ? n * 60_000 : n
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parseCsv(value: string | undefined): Set<string> {
|
|
139
|
+
if (!value) return DEFAULT_INCLUDES
|
|
140
|
+
const out = new Set<string>()
|
|
141
|
+
for (const raw of value.split(',')) {
|
|
142
|
+
const trimmed = raw.trim().toLowerCase()
|
|
143
|
+
if (trimmed) out.add(trimmed)
|
|
144
|
+
}
|
|
145
|
+
return out
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function valueOf(args: string[], flag: string): string | undefined {
|
|
149
|
+
const idx = args.indexOf(flag)
|
|
150
|
+
if (idx >= 0 && idx + 1 < args.length) return args[idx + 1]
|
|
151
|
+
const prefix = `${flag}=`
|
|
152
|
+
return args.find((a) => a.startsWith(prefix))?.slice(prefix.length)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function numberFlag(args: string[], flag: string, fallback: number): number {
|
|
156
|
+
const raw = valueOf(args, flag)
|
|
157
|
+
if (raw === undefined) return fallback
|
|
158
|
+
const n = Number(raw)
|
|
159
|
+
return Number.isFinite(n) && n > 0 ? Math.round(n) : fallback
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface TimelineSlice {
|
|
163
|
+
events: SootSimTimelineEvent[]
|
|
164
|
+
byKind: Record<string, number>
|
|
165
|
+
total: number
|
|
166
|
+
errors: SootSimTimelineEvent[]
|
|
167
|
+
alerts: SootSimTimelineEvent[]
|
|
168
|
+
screens: SootSimTimelineEvent[]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function fetchTimeline(bridge: WsBridge, sinceMs: number): Promise<TimelineSlice> {
|
|
172
|
+
const query: SootSimTimelineQuery = {
|
|
173
|
+
limit: 500,
|
|
174
|
+
since: Date.now() - sinceMs,
|
|
175
|
+
}
|
|
176
|
+
const result = await callInBridge<SootSimTimelineQueryResult>(
|
|
177
|
+
bridge,
|
|
178
|
+
'SootSim.bridges.timeline.recent',
|
|
179
|
+
query,
|
|
180
|
+
)
|
|
181
|
+
const events = result.events ?? []
|
|
182
|
+
const byKind: Record<string, number> = {}
|
|
183
|
+
const errors: SootSimTimelineEvent[] = []
|
|
184
|
+
const alerts: SootSimTimelineEvent[] = []
|
|
185
|
+
const screens: SootSimTimelineEvent[] = []
|
|
186
|
+
for (const ev of events) {
|
|
187
|
+
byKind[ev.kind] = (byKind[ev.kind] ?? 0) + 1
|
|
188
|
+
const data = ev.data as Record<string, unknown> | null
|
|
189
|
+
const level = data && typeof data === 'object' ? (data.level as string) : undefined
|
|
190
|
+
if (ev.kind === 'console' && (level === 'error' || level === 'warn')) {
|
|
191
|
+
errors.push(ev)
|
|
192
|
+
}
|
|
193
|
+
if (ev.kind === 'alert' || ev.kind === 'actionsheet' || ev.kind === 'picker') {
|
|
194
|
+
alerts.push(ev)
|
|
195
|
+
}
|
|
196
|
+
if (ev.kind === 'screen' || ev.kind === 'route') {
|
|
197
|
+
screens.push(ev)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
events,
|
|
202
|
+
byKind,
|
|
203
|
+
total: events.length,
|
|
204
|
+
errors,
|
|
205
|
+
alerts,
|
|
206
|
+
screens,
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
interface NetworkSlice {
|
|
211
|
+
total: number
|
|
212
|
+
failed: NetworkEntry[]
|
|
213
|
+
slow: NetworkEntry[]
|
|
214
|
+
inFlight: number
|
|
215
|
+
slowThresholdMs: number
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function fetchNetwork(
|
|
219
|
+
bridge: WsBridge,
|
|
220
|
+
sinceMs: number,
|
|
221
|
+
slowThresholdMs: number,
|
|
222
|
+
): Promise<NetworkSlice | { error: string }> {
|
|
223
|
+
// re-uses the observability store the existing `network` command
|
|
224
|
+
// reads from. local filtering keeps this command independent of any
|
|
225
|
+
// new bridge surface and lets it run against today's engine.
|
|
226
|
+
const result = (await bridge.send({
|
|
227
|
+
type: 'evaluate',
|
|
228
|
+
code: `(() => {
|
|
229
|
+
const obs = window.__sootsimObservability;
|
|
230
|
+
if (!obs) return { ok: false };
|
|
231
|
+
return { ok: true, entries: obs.network.getSnapshot() };
|
|
232
|
+
})()`,
|
|
233
|
+
})) as { ok: boolean; entries?: NetworkEntry[] }
|
|
234
|
+
if (!result || !result.ok) {
|
|
235
|
+
return { error: 'observability bridge not installed' }
|
|
236
|
+
}
|
|
237
|
+
const all = result.entries ?? []
|
|
238
|
+
const cutoff = Date.now() - sinceMs
|
|
239
|
+
// observability ring uses startTs; older shapes used startedAt. accept
|
|
240
|
+
// both so this command works against both shapes without a feature
|
|
241
|
+
// detection round-trip.
|
|
242
|
+
const inWindow = all.filter((e) => (e.startTs ?? e.startedAt ?? 0) >= cutoff)
|
|
243
|
+
const failed = inWindow.filter(
|
|
244
|
+
(e) => !!e.error || (e.status != null && e.status >= 400),
|
|
245
|
+
)
|
|
246
|
+
const slow = inWindow
|
|
247
|
+
.filter((e) => e.durationMs != null && e.durationMs >= slowThresholdMs)
|
|
248
|
+
.sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0))
|
|
249
|
+
const inFlight = inWindow.filter((e) => e.durationMs == null).length
|
|
250
|
+
return {
|
|
251
|
+
total: inWindow.length,
|
|
252
|
+
failed,
|
|
253
|
+
slow,
|
|
254
|
+
inFlight,
|
|
255
|
+
slowThresholdMs,
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
interface ReactSlice {
|
|
260
|
+
commits: number
|
|
261
|
+
slow: SlowRow[]
|
|
262
|
+
rerenders: RerenderRow[]
|
|
263
|
+
durationWarning?: string
|
|
264
|
+
error?: string
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function fetchReact(bridge: WsBridge, limit: number): Promise<ReactSlice> {
|
|
268
|
+
const [slowResult, rerendersResult] = (await Promise.all([
|
|
269
|
+
callInBridge(bridge, 'SootSim.bridges.reactProfile.slow', { limit }),
|
|
270
|
+
callInBridge(bridge, 'SootSim.bridges.reactProfile.rerenders', { limit }),
|
|
271
|
+
])) as [
|
|
272
|
+
{
|
|
273
|
+
rows?: SlowRow[]
|
|
274
|
+
commits?: number
|
|
275
|
+
error?: string
|
|
276
|
+
durationWarning?: string
|
|
277
|
+
},
|
|
278
|
+
{ rows?: RerenderRow[]; commits?: number; error?: string },
|
|
279
|
+
]
|
|
280
|
+
return {
|
|
281
|
+
commits: slowResult.commits ?? rerendersResult.commits ?? 0,
|
|
282
|
+
slow: slowResult.rows ?? [],
|
|
283
|
+
rerenders: rerendersResult.rows ?? [],
|
|
284
|
+
durationWarning: slowResult.durationWarning,
|
|
285
|
+
error: slowResult.error ?? rerendersResult.error,
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function fetchConsoleErrors(
|
|
290
|
+
bridge: WsBridge,
|
|
291
|
+
sinceMs: number,
|
|
292
|
+
): Promise<{ entries: LogEntry[]; total: number } | { error: string }> {
|
|
293
|
+
const result = (await bridge.send({
|
|
294
|
+
type: 'evaluate',
|
|
295
|
+
code: `(() => {
|
|
296
|
+
const obs = window.__sootsimObservability;
|
|
297
|
+
if (!obs) return { ok: false };
|
|
298
|
+
return { ok: true, entries: obs.logs.getSnapshot() };
|
|
299
|
+
})()`,
|
|
300
|
+
})) as { ok: boolean; entries?: LogEntry[] }
|
|
301
|
+
if (!result || !result.ok) {
|
|
302
|
+
return { error: 'observability bridge not installed' }
|
|
303
|
+
}
|
|
304
|
+
const cutoff = Date.now() - sinceMs
|
|
305
|
+
const all = result.entries ?? []
|
|
306
|
+
const errs = all.filter(
|
|
307
|
+
(e) => logTs(e) >= cutoff && (e.level === 'error' || e.level === 'warn'),
|
|
308
|
+
)
|
|
309
|
+
return { entries: errs.slice(-20), total: errs.length }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function formatNetworkRow(entry: NetworkEntry): string {
|
|
313
|
+
const status = entry.error ? 'err' : entry.status != null ? String(entry.status) : '...'
|
|
314
|
+
const method = (entry.method ?? 'GET').padEnd(6)
|
|
315
|
+
const dur = entry.durationMs != null ? `${entry.durationMs.toFixed(0)}ms` : '...'
|
|
316
|
+
const url = entry.displayUrl ?? entry.url
|
|
317
|
+
// shorten long urls to 96 chars in the diagnose report — full
|
|
318
|
+
// detail is one `rnx network get <id>` away.
|
|
319
|
+
const shortUrl = url.length > 96 ? `${url.slice(0, 92)}…` : url
|
|
320
|
+
return ` ${status.padStart(3)} ${method} ${dur.padStart(7)} ${shortUrl} (${entry.id})`
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function formatTimelineRow(ev: SootSimTimelineEvent, anchorMs: number): string {
|
|
324
|
+
const dt = ((ev.t - anchorMs) / 1000).toFixed(2)
|
|
325
|
+
const sign = ev.t >= anchorMs ? '-' : '+'
|
|
326
|
+
const data = ev.data as Record<string, unknown> | null
|
|
327
|
+
let payload = ''
|
|
328
|
+
if (data && typeof data === 'object') {
|
|
329
|
+
const message = (data.message ?? '').toString().slice(0, 120)
|
|
330
|
+
const title = (data.title ?? data.name ?? data.path ?? data.url ?? '').toString()
|
|
331
|
+
const phase = (data.phase ?? '').toString()
|
|
332
|
+
payload = [phase, title, message].filter(Boolean).join(' · ')
|
|
333
|
+
}
|
|
334
|
+
return ` -${dt.padStart(5)}s [${ev.kind}] ${payload}`
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function formatTable(headers: string[], rows: string[][], indent: string): string {
|
|
338
|
+
if (rows.length === 0) return `${indent}(no rows)`
|
|
339
|
+
const widths = headers.map((h, i) =>
|
|
340
|
+
Math.max(h.length, ...rows.map((row) => row[i]?.length ?? 0)),
|
|
341
|
+
)
|
|
342
|
+
const line = (cells: string[]) =>
|
|
343
|
+
cells
|
|
344
|
+
.map((cell, i) => cell.padEnd(widths[i]))
|
|
345
|
+
.join(' ')
|
|
346
|
+
.trimEnd()
|
|
347
|
+
return [
|
|
348
|
+
line(headers),
|
|
349
|
+
line(headers.map((h) => '-'.repeat(h.length))),
|
|
350
|
+
...rows.map(line),
|
|
351
|
+
]
|
|
352
|
+
.map((row) => `${indent}${row}`)
|
|
353
|
+
.join('\n')
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export async function runDiagnose(
|
|
357
|
+
args: string[],
|
|
358
|
+
opts: DiagnoseOptions,
|
|
359
|
+
): Promise<number> {
|
|
360
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
361
|
+
printHelp()
|
|
362
|
+
return 0
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const parsed = parseBridgeCliArgs(args, {
|
|
366
|
+
port: opts.port,
|
|
367
|
+
stripBooleanFlags: ['--json', '--help', '-h'],
|
|
368
|
+
stripValueFlags: ['--since', '--include', '--limit', '--slow-threshold'],
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
const sub = parsed.positional[0] ?? 'recent'
|
|
372
|
+
if (sub !== 'recent') {
|
|
373
|
+
console.error(` unknown subcommand: ${sub}`)
|
|
374
|
+
console.error(' did you mean: rnx get diagnosis ?')
|
|
375
|
+
return 1
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const sinceMs = parseSinceMs(valueOf(args, '--since'), 90_000)
|
|
379
|
+
const include = parseCsv(valueOf(args, '--include'))
|
|
380
|
+
const reactLimit = numberFlag(args, '--limit', 8)
|
|
381
|
+
const slowThresholdMs = numberFlag(args, '--slow-threshold', 1000)
|
|
382
|
+
const wantsJson = args.includes('--json')
|
|
383
|
+
|
|
384
|
+
const bridge = createBridgeFromParsed(parsed)
|
|
385
|
+
try {
|
|
386
|
+
// run every requested dimension in parallel — one bridge round-trip
|
|
387
|
+
// per primitive, no chained CLI invocations. on a healthy sim
|
|
388
|
+
// this finishes in a single tick.
|
|
389
|
+
const tasks: Array<Promise<unknown>> = []
|
|
390
|
+
const ordered: string[] = []
|
|
391
|
+
if (include.has('timeline') || include.has('screen') || include.has('alert')) {
|
|
392
|
+
ordered.push('timeline')
|
|
393
|
+
tasks.push(fetchTimeline(bridge, sinceMs))
|
|
394
|
+
}
|
|
395
|
+
if (include.has('network') || include.has('fetch')) {
|
|
396
|
+
ordered.push('network')
|
|
397
|
+
tasks.push(fetchNetwork(bridge, sinceMs, slowThresholdMs))
|
|
398
|
+
}
|
|
399
|
+
if (include.has('react')) {
|
|
400
|
+
ordered.push('react')
|
|
401
|
+
tasks.push(fetchReact(bridge, reactLimit))
|
|
402
|
+
}
|
|
403
|
+
if (include.has('console')) {
|
|
404
|
+
ordered.push('console')
|
|
405
|
+
tasks.push(fetchConsoleErrors(bridge, sinceMs))
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// always probe the engine build id (one cheap eval). a reused sim
|
|
409
|
+
// session on a stale engine silently produced false QA verdicts
|
|
410
|
+
// (QA F20-5); surfacing the build here makes the staleness checkable.
|
|
411
|
+
const engineBuild = await bridge
|
|
412
|
+
.send({
|
|
413
|
+
type: 'evaluate',
|
|
414
|
+
code: `globalThis.__sootsimEngineBuild ?? null`,
|
|
415
|
+
})
|
|
416
|
+
.then((v) => (typeof v === 'string' ? v : null))
|
|
417
|
+
.catch(() => null)
|
|
418
|
+
|
|
419
|
+
const results = await Promise.all(tasks)
|
|
420
|
+
const slices: Record<string, unknown> = {}
|
|
421
|
+
for (let i = 0; i < ordered.length; i++) slices[ordered[i]] = results[i]
|
|
422
|
+
|
|
423
|
+
if (wantsJson) {
|
|
424
|
+
console.log(
|
|
425
|
+
JSON.stringify(
|
|
426
|
+
{
|
|
427
|
+
generatedAt: Date.now(),
|
|
428
|
+
engineBuild,
|
|
429
|
+
sinceMs,
|
|
430
|
+
include: Array.from(include),
|
|
431
|
+
slices,
|
|
432
|
+
},
|
|
433
|
+
null,
|
|
434
|
+
2,
|
|
435
|
+
),
|
|
436
|
+
)
|
|
437
|
+
return 0
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// human render — one section per slice. terse, scannable, demo-paste-friendly.
|
|
441
|
+
const now = Date.now()
|
|
442
|
+
console.log(
|
|
443
|
+
` diagnose recent (last ${(sinceMs / 1000).toFixed(0)}s, include: ${Array.from(include).sort().join(',')}):`,
|
|
444
|
+
)
|
|
445
|
+
console.log(
|
|
446
|
+
` engine build: ${engineBuild ?? 'unknown (no build stamp — likely a stale or pre-F20-5 engine)'}`,
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
if (slices.timeline) {
|
|
450
|
+
const t = slices.timeline as TimelineSlice
|
|
451
|
+
console.log(`\n timeline — ${t.total} event(s) in window`)
|
|
452
|
+
const summary = Object.entries(t.byKind)
|
|
453
|
+
.sort((a, b) => b[1] - a[1])
|
|
454
|
+
.map(([k, n]) => `${n} ${k}`)
|
|
455
|
+
.join(' · ')
|
|
456
|
+
if (summary) console.log(` summary: ${summary}`)
|
|
457
|
+
if (t.screens.length) {
|
|
458
|
+
const last = t.screens[t.screens.length - 1]
|
|
459
|
+
const data = last.data as Record<string, unknown> | null
|
|
460
|
+
const name = data ? (data.name ?? data.activeName ?? data.path ?? '?') : '?'
|
|
461
|
+
console.log(` last screen: ${String(name)}`)
|
|
462
|
+
}
|
|
463
|
+
if (t.alerts.length) {
|
|
464
|
+
console.log(` alerts (${t.alerts.length}):`)
|
|
465
|
+
for (const a of t.alerts.slice(-5)) {
|
|
466
|
+
console.log(formatTimelineRow(a, now))
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (t.errors.length) {
|
|
470
|
+
console.log(` console errors/warns from timeline (${t.errors.length}):`)
|
|
471
|
+
for (const e of t.errors.slice(-5)) {
|
|
472
|
+
console.log(formatTimelineRow(e, now))
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (slices.network) {
|
|
478
|
+
const n = slices.network as NetworkSlice | { error: string }
|
|
479
|
+
if ('error' in n) {
|
|
480
|
+
console.log(`\n network — ${n.error}`)
|
|
481
|
+
} else {
|
|
482
|
+
console.log(
|
|
483
|
+
`\n network — ${n.total} request(s), ${n.failed.length} failed, ${n.slow.length} slow (>${n.slowThresholdMs}ms), ${n.inFlight} in-flight`,
|
|
484
|
+
)
|
|
485
|
+
if (n.failed.length) {
|
|
486
|
+
console.log(` failed:`)
|
|
487
|
+
for (const e of n.failed.slice(-8)) console.log(formatNetworkRow(e))
|
|
488
|
+
}
|
|
489
|
+
if (n.slow.length) {
|
|
490
|
+
console.log(` slowest:`)
|
|
491
|
+
for (const e of n.slow.slice(0, 5)) console.log(formatNetworkRow(e))
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (slices.react) {
|
|
497
|
+
const r = slices.react as ReactSlice
|
|
498
|
+
console.log(`\n react — ${r.commits} commit(s)`)
|
|
499
|
+
if (r.error) console.log(` warning: ${r.error}`)
|
|
500
|
+
if (r.durationWarning) console.log(` warning: ${r.durationWarning}`)
|
|
501
|
+
const allZero =
|
|
502
|
+
r.slow.length > 0 && r.slow.every((row) => row.totalMs <= 0 && row.slowestMs <= 0)
|
|
503
|
+
if (allZero && !r.durationWarning) {
|
|
504
|
+
console.log(
|
|
505
|
+
` warning: every commit reports actualDuration=0ms — bundle has React profiler timer disabled`,
|
|
506
|
+
)
|
|
507
|
+
}
|
|
508
|
+
if (r.slow.length) {
|
|
509
|
+
console.log(' slowest:')
|
|
510
|
+
console.log(
|
|
511
|
+
formatTable(
|
|
512
|
+
['component', 'total', 'commits', 'slowest'],
|
|
513
|
+
r.slow
|
|
514
|
+
.slice(0, reactLimit)
|
|
515
|
+
.map((row) => [
|
|
516
|
+
row.displayName,
|
|
517
|
+
`${row.totalMs.toFixed(2)}ms`,
|
|
518
|
+
String(row.commitCount),
|
|
519
|
+
`${row.slowestMs.toFixed(2)}ms`,
|
|
520
|
+
]),
|
|
521
|
+
' ',
|
|
522
|
+
),
|
|
523
|
+
)
|
|
524
|
+
}
|
|
525
|
+
if (r.rerenders.length) {
|
|
526
|
+
console.log(' most rerenders:')
|
|
527
|
+
console.log(
|
|
528
|
+
formatTable(
|
|
529
|
+
['component', 'renders', 'top causes'],
|
|
530
|
+
r.rerenders.slice(0, reactLimit).map((row) => {
|
|
531
|
+
const causes = Object.entries(row.causes ?? {})
|
|
532
|
+
.filter(([, v]) => v > 0)
|
|
533
|
+
.sort((a, b) => b[1] - a[1])
|
|
534
|
+
.slice(0, 3)
|
|
535
|
+
.map(([k, v]) => `${k}:${v}`)
|
|
536
|
+
.join(', ')
|
|
537
|
+
return [
|
|
538
|
+
row.displayName,
|
|
539
|
+
String(row.renders ?? row.count ?? 0),
|
|
540
|
+
causes || '-',
|
|
541
|
+
]
|
|
542
|
+
}),
|
|
543
|
+
' ',
|
|
544
|
+
),
|
|
545
|
+
)
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (slices.console) {
|
|
550
|
+
const c = slices.console as
|
|
551
|
+
| { entries: LogEntry[]; total: number }
|
|
552
|
+
| { error: string }
|
|
553
|
+
if ('error' in c) {
|
|
554
|
+
console.log(`\n console — ${c.error}`)
|
|
555
|
+
} else {
|
|
556
|
+
console.log(`\n console errors/warns — ${c.total} in window`)
|
|
557
|
+
for (const log of c.entries.slice(-8)) {
|
|
558
|
+
const dt = ((logTs(log) - now) / 1000).toFixed(2)
|
|
559
|
+
const msg = logMessage(log).slice(0, 160)
|
|
560
|
+
console.log(` -${dt.padStart(5)}s [${log.level}] ${msg}`)
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// close with the per-identity cursor advance hint, mirroring
|
|
566
|
+
// what-happened's footer.
|
|
567
|
+
const identityKey = getCliIdentityKey()
|
|
568
|
+
if (identityKey) {
|
|
569
|
+
console.log(
|
|
570
|
+
`\n next: rnx what-happened (cursor advances) · rnx network get <id> · rnx perf react why <fiber-id>`,
|
|
571
|
+
)
|
|
572
|
+
}
|
|
573
|
+
return 0
|
|
574
|
+
} catch (err) {
|
|
575
|
+
// a bridge call rejecting (most commonly "no sim connected") must not
|
|
576
|
+
// escape as a raw stack trace with a 0 exit code — `get diagnosis` is the
|
|
577
|
+
// "what just went wrong" command; failing it should read cleanly and
|
|
578
|
+
// exit non-zero so `rnx get diagnosis && …` chains behave.
|
|
579
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
580
|
+
if (/no sim connected/i.test(msg)) {
|
|
581
|
+
console.error(' no sim connected — open one first: rnx open <metroPort>')
|
|
582
|
+
} else {
|
|
583
|
+
console.error(` diagnose failed: ${msg}`)
|
|
584
|
+
}
|
|
585
|
+
return 1
|
|
586
|
+
} finally {
|
|
587
|
+
bridge.close()
|
|
588
|
+
}
|
|
589
|
+
}
|