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,481 @@
|
|
|
1
|
+
// rnx detox — run existing detox suites against rnx
|
|
2
|
+
//
|
|
3
|
+
// use the customer's jest config unchanged. the rnx preset maps the detox
|
|
4
|
+
// driver and owns browser cleanup; jest owns setup, selection and deadlines.
|
|
5
|
+
|
|
6
|
+
import { spawn } from 'child_process'
|
|
7
|
+
import {
|
|
8
|
+
existsSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
statSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
unlinkSync,
|
|
14
|
+
} from 'fs'
|
|
15
|
+
import { createRequire } from 'module'
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
17
|
+
import { tmpdir } from 'os'
|
|
18
|
+
import { dirname, resolve, join, relative } from 'path'
|
|
19
|
+
import { rnxPublicBrand } from '../../src/public-brand'
|
|
20
|
+
import { resolveCliAuth } from '../auth'
|
|
21
|
+
import { registerRun, shouldRegisterRun } from '../run-registry'
|
|
22
|
+
import { rnxExit } from '../run-rnx'
|
|
23
|
+
import { buildShellUrl } from './control'
|
|
24
|
+
import { discoverSootsimUrl } from './flow'
|
|
25
|
+
import { resolveDefaultUploadOrigin } from './upload'
|
|
26
|
+
|
|
27
|
+
interface RunDetoxOpts {
|
|
28
|
+
port?: number
|
|
29
|
+
verbose?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const HELP = `
|
|
33
|
+
rnx detox — run detox-style tests against an rnx shell
|
|
34
|
+
|
|
35
|
+
usage:
|
|
36
|
+
rnx detox [testFiles...] run all tests under e2e/, test/e2e/, or detox/
|
|
37
|
+
rnx detox --config <path> use a specific jest config
|
|
38
|
+
rnx detox init scaffold rnx-detox.config.cjs + sample test
|
|
39
|
+
rnx detox --watch jest watch mode
|
|
40
|
+
rnx detox --headed keep the rnx shell window visible
|
|
41
|
+
rnx detox -t <pattern> pass a jest --testNamePattern
|
|
42
|
+
rnx detox --no-launch skip auto-launching an rnx shell
|
|
43
|
+
|
|
44
|
+
rnx detox auto-launches an rnx shell if none is running on the
|
|
45
|
+
expected port. disable with --no-launch if you're managing the shell yourself.
|
|
46
|
+
`
|
|
47
|
+
|
|
48
|
+
export async function runDetox(args: string[], opts: RunDetoxOpts = {}): Promise<void> {
|
|
49
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
50
|
+
console.log(HELP)
|
|
51
|
+
rnxExit(0)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (args[0] === 'init') {
|
|
55
|
+
return initDetoxProject()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const getFlag = (name: string) => {
|
|
59
|
+
const inline = args.find((arg) => arg.startsWith(`${name}=`))
|
|
60
|
+
if (inline) return inline.slice(name.length + 1)
|
|
61
|
+
const i = args.indexOf(name)
|
|
62
|
+
return i >= 0 ? args[i + 1] : undefined
|
|
63
|
+
}
|
|
64
|
+
const hasFlag = (name: string) => args.includes(name)
|
|
65
|
+
|
|
66
|
+
const watch = hasFlag('--watch') || hasFlag('--watchAll')
|
|
67
|
+
const headed = hasFlag('--headed')
|
|
68
|
+
const noLaunch = hasFlag('--no-launch')
|
|
69
|
+
const configFlag = getFlag('--config')
|
|
70
|
+
const port =
|
|
71
|
+
Number(getFlag('--port')) || opts.port || Number(process.env.SOOTSIM_PORT) || 5173
|
|
72
|
+
if (getFlag('--sim')) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
'Detox owns its browser context; pin its shell with RNX_URL and --device instead of --sim',
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// consume only rnx options. jest must receive its complete argv, including
|
|
79
|
+
// selection, timeouts, reporters, coverage, and options added by future jest versions.
|
|
80
|
+
const jestArgs: string[] = ['jest']
|
|
81
|
+
for (let index = 0; index < args.length; index++) {
|
|
82
|
+
const arg = args[index]
|
|
83
|
+
if (arg === '--headed' || arg === '--no-launch') continue
|
|
84
|
+
const name = arg.split('=', 1)[0]
|
|
85
|
+
if (['--config', '--app', '--device', '--port'].includes(name)) {
|
|
86
|
+
if (!arg.includes('=')) {
|
|
87
|
+
if (!args[index + 1] || args[index + 1].startsWith('-')) {
|
|
88
|
+
throw new Error(`${name} requires a value`)
|
|
89
|
+
}
|
|
90
|
+
index++
|
|
91
|
+
}
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
jestArgs.push(arg === '--grep' ? '--testNamePattern' : arg)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let localConfig =
|
|
98
|
+
configFlag ||
|
|
99
|
+
firstExisting([
|
|
100
|
+
'rnx-detox.config.cjs',
|
|
101
|
+
'jest.config.ts',
|
|
102
|
+
'jest.config.mts',
|
|
103
|
+
'jest.config.mjs',
|
|
104
|
+
'jest.config.cjs',
|
|
105
|
+
'jest.config.js',
|
|
106
|
+
'jest.config.cts',
|
|
107
|
+
'jest.config.json',
|
|
108
|
+
])
|
|
109
|
+
if (!localConfig && existsSync('package.json')) {
|
|
110
|
+
const pkg = JSON.parse(readFileSync('package.json', 'utf8'))
|
|
111
|
+
if (pkg.jest) localConfig = 'package.json'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// test dir discovery. external test suites set RNX_TEST_DIR; otherwise
|
|
115
|
+
// we auto-detect the conventional locations.
|
|
116
|
+
const detoxDirs = ['e2e', 'test/e2e', 'detox']
|
|
117
|
+
let testDir: string | null = process.env.RNX_TEST_DIR
|
|
118
|
+
? resolve(process.cwd(), process.env.RNX_TEST_DIR)
|
|
119
|
+
: null
|
|
120
|
+
if (!testDir && !localConfig && args[0] && !args[0].startsWith('-')) {
|
|
121
|
+
const target = resolve(args[0])
|
|
122
|
+
if (existsSync(target))
|
|
123
|
+
testDir = statSync(target).isDirectory() ? target : dirname(target)
|
|
124
|
+
}
|
|
125
|
+
if (!testDir && !localConfig) {
|
|
126
|
+
for (const dir of detoxDirs) {
|
|
127
|
+
const full = resolve(process.cwd(), dir)
|
|
128
|
+
if (existsSync(full)) {
|
|
129
|
+
testDir = full
|
|
130
|
+
break
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!testDir && !localConfig) {
|
|
135
|
+
console.error(
|
|
136
|
+
` error: no detox tests found. expected one of: ${detoxDirs.join(', ')}\n` +
|
|
137
|
+
` run 'rnx detox init' to scaffold a starter suite.`,
|
|
138
|
+
)
|
|
139
|
+
rnxExit(1)
|
|
140
|
+
}
|
|
141
|
+
testDir ??= process.cwd()
|
|
142
|
+
|
|
143
|
+
// auto-launch: make sure an rnx shell is reachable before running jest.
|
|
144
|
+
// Reuse the Maestro discovery path: scan local dev servers.
|
|
145
|
+
// and `rnx open` the first match.
|
|
146
|
+
if (!noLaunch) {
|
|
147
|
+
await ensureShellRunning(port)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// build the jest command. prefer the user's config if they passed --config
|
|
151
|
+
// or if a rnx-detox.config.cjs exists in cwd. otherwise write a temporary
|
|
152
|
+
// config that loads our preset.
|
|
153
|
+
let tempConfig: string | null = null
|
|
154
|
+
if (localConfig) {
|
|
155
|
+
jestArgs.push(
|
|
156
|
+
'--config',
|
|
157
|
+
localConfig.startsWith('{') ? localConfig : resolve(process.cwd(), localConfig),
|
|
158
|
+
)
|
|
159
|
+
} else {
|
|
160
|
+
// no local config — write a temp config that pulls in our preset.
|
|
161
|
+
// jest's --preset flag has quirky module resolution that breaks with
|
|
162
|
+
// subpath exports, so we generate a real config file instead.
|
|
163
|
+
// scope roots + testMatch to the discovered test dir so jest doesn't
|
|
164
|
+
// scan the entire repo (which causes haste collisions in monorepos).
|
|
165
|
+
const presetPath = fileURLToPath(
|
|
166
|
+
import.meta.resolve(`${rnxPublicBrand.packageName}/detox/jest-preset`),
|
|
167
|
+
)
|
|
168
|
+
// an external suite may optionally declare its own ignore patterns via e2e/jest-ignore.cjs
|
|
169
|
+
const suiteIgnoreFile = testDir ? join(testDir, 'e2e', 'jest-ignore.cjs') : null
|
|
170
|
+
let suiteIgnorePatterns: string[] = []
|
|
171
|
+
if (suiteIgnoreFile && existsSync(suiteIgnoreFile)) {
|
|
172
|
+
const req = createRequire(import.meta.url)
|
|
173
|
+
const exported = req(suiteIgnoreFile)
|
|
174
|
+
const patterns = Array.isArray(exported)
|
|
175
|
+
? exported
|
|
176
|
+
: exported.testPathIgnorePatterns
|
|
177
|
+
if (
|
|
178
|
+
!Array.isArray(patterns) ||
|
|
179
|
+
!patterns.every((pattern) => typeof pattern === 'string')
|
|
180
|
+
) {
|
|
181
|
+
throw new Error(`${suiteIgnoreFile} must export string ignore patterns`)
|
|
182
|
+
}
|
|
183
|
+
suiteIgnorePatterns = patterns
|
|
184
|
+
}
|
|
185
|
+
tempConfig = join(tmpdir(), `rnx-detox-${process.pid}.config.cjs`)
|
|
186
|
+
const cfg = {
|
|
187
|
+
rootDir: testDir,
|
|
188
|
+
roots: ['<rootDir>'],
|
|
189
|
+
testMatch: ['<rootDir>/**/*.test.ts', '<rootDir>/**/*.test.js'],
|
|
190
|
+
...(suiteIgnorePatterns.length > 0
|
|
191
|
+
? { testPathIgnorePatterns: suiteIgnorePatterns }
|
|
192
|
+
: {}),
|
|
193
|
+
}
|
|
194
|
+
writeFileSync(
|
|
195
|
+
tempConfig,
|
|
196
|
+
`module.exports = { ...require(${JSON.stringify(presetPath)}), ...${JSON.stringify(cfg)} }\n`,
|
|
197
|
+
)
|
|
198
|
+
jestArgs.push('--config', tempConfig)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// hosted run registration (org dashboard pass/fail row). when it applies,
|
|
202
|
+
// capture jest's structured results via --json --outputFile so the row can
|
|
203
|
+
// carry test counts + the first failure; console output stays untouched.
|
|
204
|
+
const auth = resolveCliAuth()
|
|
205
|
+
const wantsRegistration =
|
|
206
|
+
!watch &&
|
|
207
|
+
(process.env.RNX_REQUIRE_REGISTRATION === 'true' ||
|
|
208
|
+
shouldRegisterRun({ uploadedShare: false, auth }))
|
|
209
|
+
let resultsFile: string | null =
|
|
210
|
+
getFlag('--outputFile') || process.env.SOOTSIM_JEST_OUTPUT_FILE || null
|
|
211
|
+
if ((process.env.SOOTSIM_JEST_JSON === '1' || wantsRegistration) && !hasFlag('--json'))
|
|
212
|
+
jestArgs.push('--json')
|
|
213
|
+
if (!getFlag('--outputFile') && process.env.SOOTSIM_JEST_OUTPUT_FILE) {
|
|
214
|
+
jestArgs.push('--outputFile', process.env.SOOTSIM_JEST_OUTPUT_FILE)
|
|
215
|
+
} else if (!resultsFile && wantsRegistration) {
|
|
216
|
+
resultsFile = join(tmpdir(), `rnx-detox-results-${process.pid}.json`)
|
|
217
|
+
jestArgs.push('--outputFile', resultsFile)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.log(` rnx detox`)
|
|
221
|
+
console.log(` test dir: ${testDir}`)
|
|
222
|
+
console.log(` port: ${port}${headed ? ' (headed)' : ''}`)
|
|
223
|
+
if (localConfig) console.log(` config: ${localConfig}`)
|
|
224
|
+
|
|
225
|
+
const shellBaseUrl = process.env.RNX_URL || `http://localhost:${port}`
|
|
226
|
+
const app = getFlag('--app')
|
|
227
|
+
const shellUrl = new URL(app ? await buildShellUrl(app, shellBaseUrl) : shellBaseUrl)
|
|
228
|
+
const device = getFlag('--device')
|
|
229
|
+
if (device) shellUrl.searchParams.set('device', device)
|
|
230
|
+
|
|
231
|
+
// proof capture runs under the repo's pinned node runtime. its v8 collector
|
|
232
|
+
// crashes in ClearStaleLeftTrimmedPointerVisitor when Sparkplug baseline
|
|
233
|
+
// compilation (Builtins_BaselineOutOfLinePrologue) pushes untagged register
|
|
234
|
+
// values onto the stack across CEntry, which MarkCompact root iteration
|
|
235
|
+
// then visits as tagged heap pointers. disable both Maglev and Sparkplug so
|
|
236
|
+
// execution stays on the stable Ignition + TurboFan pipeline under Node 24.
|
|
237
|
+
// normal user detox runs retain their node defaults and resolve jest through npx.
|
|
238
|
+
const proofCapture =
|
|
239
|
+
process.env.CONFORMANCE_PROOF === '1' ||
|
|
240
|
+
!!process.env.CONFORMANCE_PROOF_DIR ||
|
|
241
|
+
process.env.CONFORMANCE_TARGET === 'sootsim'
|
|
242
|
+
const command = proofCapture ? 'node' : 'npx'
|
|
243
|
+
const commandArgs = proofCapture
|
|
244
|
+
? [
|
|
245
|
+
'--no-maglev',
|
|
246
|
+
'--no-sparkplug',
|
|
247
|
+
createRequire(join(process.cwd(), 'package.json')).resolve('jest/bin/jest'),
|
|
248
|
+
...jestArgs.slice(1),
|
|
249
|
+
]
|
|
250
|
+
: jestArgs
|
|
251
|
+
const child = spawn(command, commandArgs, {
|
|
252
|
+
cwd: process.cwd(),
|
|
253
|
+
stdio: 'inherit',
|
|
254
|
+
env: {
|
|
255
|
+
...process.env,
|
|
256
|
+
RNX_TEST_DIR: testDir,
|
|
257
|
+
SOOTSIM_PORT: String(port),
|
|
258
|
+
SOOTSIM_HEADED: headed ? '1' : '',
|
|
259
|
+
RNX_URL: shellUrl.toString(),
|
|
260
|
+
},
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
// jest owns the browsers, and it only reaps them from its own signal and exit
|
|
264
|
+
// handlers. this process is the one a caller has a pid for, so a SIGTERM aimed
|
|
265
|
+
// at `rnx detox` lands here and jest never hears about it: it is a
|
|
266
|
+
// grandchild (this -> npx -> jest), so it keeps running with no parent and
|
|
267
|
+
// every chrome it launched is orphaned for good. forward the signal instead of
|
|
268
|
+
// exiting out from under it, and let its exit code end this process.
|
|
269
|
+
const forward = (signal: NodeJS.Signals) => () => {
|
|
270
|
+
child.kill(signal)
|
|
271
|
+
}
|
|
272
|
+
const onTerm = forward('SIGTERM')
|
|
273
|
+
const onInt = forward('SIGINT')
|
|
274
|
+
process.on('SIGTERM', onTerm)
|
|
275
|
+
process.on('SIGINT', onInt)
|
|
276
|
+
|
|
277
|
+
const startedAt = Date.now()
|
|
278
|
+
let code: number
|
|
279
|
+
try {
|
|
280
|
+
code = await new Promise<number>((resolveCode, reject) => {
|
|
281
|
+
child.once('error', reject)
|
|
282
|
+
child.once('exit', (exitCode, signal) => {
|
|
283
|
+
if (signal) {
|
|
284
|
+
console.error(` error: jest terminated by ${signal}`)
|
|
285
|
+
resolveCode(1)
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
resolveCode(exitCode ?? 1)
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
} finally {
|
|
292
|
+
process.off('SIGTERM', onTerm)
|
|
293
|
+
process.off('SIGINT', onInt)
|
|
294
|
+
if (tempConfig) unlinkSync(tempConfig)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (wantsRegistration) {
|
|
298
|
+
const results =
|
|
299
|
+
resultsFile && existsSync(resultsFile) && statSync(resultsFile).mtimeMs >= startedAt
|
|
300
|
+
? readJestResults(resultsFile)
|
|
301
|
+
: null
|
|
302
|
+
// a selection that skipped every test is not a passing suite.
|
|
303
|
+
if (code === 0 && results && results.numPassedTests === 0) {
|
|
304
|
+
console.error(' error: no tests were selected or executed')
|
|
305
|
+
code = 1
|
|
306
|
+
}
|
|
307
|
+
const origin = await resolveDefaultUploadOrigin()
|
|
308
|
+
const run = await registerRun({
|
|
309
|
+
origin,
|
|
310
|
+
kind: 'detox',
|
|
311
|
+
name: relative(process.cwd(), testDir) || 'e2e',
|
|
312
|
+
status: code === 0 ? 'passed' : 'failed',
|
|
313
|
+
summary: results
|
|
314
|
+
? `${results.numPassedTests}/${results.numTotalTests} tests passed`
|
|
315
|
+
: null,
|
|
316
|
+
failureMessage: code === 0 ? null : (results?.firstFailure ?? null),
|
|
317
|
+
// tests map onto the row's step summary: total count + first failing
|
|
318
|
+
// index, same shape flow lanes fill from their step trace.
|
|
319
|
+
stepCount: results?.numTotalTests ?? null,
|
|
320
|
+
failedStepIndex: results?.firstFailedIndex ?? null,
|
|
321
|
+
durationMs: Date.now() - startedAt,
|
|
322
|
+
auth,
|
|
323
|
+
})
|
|
324
|
+
if (!run) rnxExit(code || 1)
|
|
325
|
+
console.log(` run: ${run.id}`)
|
|
326
|
+
writeFileSync(
|
|
327
|
+
join(process.cwd(), 'test-result.json'),
|
|
328
|
+
JSON.stringify({
|
|
329
|
+
runId: run.id,
|
|
330
|
+
previewUrl: run.previewUrl,
|
|
331
|
+
traceUrl: run.traceUrl,
|
|
332
|
+
}),
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
if (
|
|
336
|
+
resultsFile &&
|
|
337
|
+
existsSync(resultsFile) &&
|
|
338
|
+
!getFlag('--outputFile') &&
|
|
339
|
+
!process.env.SOOTSIM_JEST_OUTPUT_FILE
|
|
340
|
+
) {
|
|
341
|
+
unlinkSync(resultsFile)
|
|
342
|
+
}
|
|
343
|
+
rnxExit(code)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export type JestRunSummary = {
|
|
347
|
+
numTotalTests: number
|
|
348
|
+
numPassedTests: number
|
|
349
|
+
firstFailure: string | null
|
|
350
|
+
firstFailedIndex: number | null
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// parse jest's --json output file down to the counts + first failure the
|
|
354
|
+
// hosted run row carries. tolerant: any parse problem returns null and the
|
|
355
|
+
// run registers with exit-code status only.
|
|
356
|
+
export function readJestResults(file: string): JestRunSummary | null {
|
|
357
|
+
try {
|
|
358
|
+
const parsed: unknown = JSON.parse(readFileSync(file, 'utf8'))
|
|
359
|
+
if (
|
|
360
|
+
!parsed ||
|
|
361
|
+
typeof parsed !== 'object' ||
|
|
362
|
+
!('numTotalTests' in parsed) ||
|
|
363
|
+
typeof parsed.numTotalTests !== 'number' ||
|
|
364
|
+
!('numPassedTests' in parsed) ||
|
|
365
|
+
typeof parsed.numPassedTests !== 'number' ||
|
|
366
|
+
!('testResults' in parsed) ||
|
|
367
|
+
!Array.isArray(parsed.testResults)
|
|
368
|
+
)
|
|
369
|
+
return null
|
|
370
|
+
let firstFailure: string | null = null
|
|
371
|
+
let firstFailedIndex: number | null = null
|
|
372
|
+
let index = 0
|
|
373
|
+
for (const suite of parsed.testResults) {
|
|
374
|
+
if (!suite || typeof suite !== 'object' || !Array.isArray(suite.assertionResults))
|
|
375
|
+
return null
|
|
376
|
+
for (const test of suite.assertionResults) {
|
|
377
|
+
if (!test || typeof test !== 'object') return null
|
|
378
|
+
if (firstFailedIndex == null && test.status === 'failed') {
|
|
379
|
+
firstFailedIndex = index
|
|
380
|
+
const messages: unknown[] = Array.isArray(test.failureMessages)
|
|
381
|
+
? test.failureMessages
|
|
382
|
+
: []
|
|
383
|
+
const message = messages
|
|
384
|
+
.filter((value): value is string => typeof value === 'string')
|
|
385
|
+
.join('\n')
|
|
386
|
+
firstFailure =
|
|
387
|
+
[typeof test.fullName === 'string' ? test.fullName : '', message]
|
|
388
|
+
.filter(Boolean)
|
|
389
|
+
.join(': ') || null
|
|
390
|
+
}
|
|
391
|
+
index += 1
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
numTotalTests: parsed.numTotalTests,
|
|
396
|
+
numPassedTests: parsed.numPassedTests,
|
|
397
|
+
firstFailure,
|
|
398
|
+
firstFailedIndex,
|
|
399
|
+
}
|
|
400
|
+
} catch {
|
|
401
|
+
return null
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function firstExisting(names: string[]): string | null {
|
|
406
|
+
for (const n of names) {
|
|
407
|
+
if (existsSync(resolve(process.cwd(), n))) return n
|
|
408
|
+
}
|
|
409
|
+
return null
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function ensureShellRunning(port: number): Promise<void> {
|
|
413
|
+
const shellUrl = await discoverSootsimUrl()
|
|
414
|
+
if (shellUrl) return
|
|
415
|
+
|
|
416
|
+
console.warn(
|
|
417
|
+
` warn: no rnx shell reachable on :${port}. start your app, then run \`rnx open <app-port>\`\n` +
|
|
418
|
+
` or pass --no-launch if you're managing the shell yourself.`,
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function initDetoxProject(): void {
|
|
423
|
+
const cwd = process.cwd()
|
|
424
|
+
const configPath = resolve(cwd, 'rnx-detox.config.cjs')
|
|
425
|
+
if (existsSync(configPath)) {
|
|
426
|
+
console.log(` skip: ${configPath} already exists`)
|
|
427
|
+
} else {
|
|
428
|
+
writeFileSync(
|
|
429
|
+
configPath,
|
|
430
|
+
`// rnx detox jest config.
|
|
431
|
+
// extends the rnx preset, which rewrites \`import ... from 'detox'\`
|
|
432
|
+
// to the rnx driver and keeps your jest transforms intact.
|
|
433
|
+
|
|
434
|
+
/** @type {import('@jest/types').Config.InitialOptions} */
|
|
435
|
+
module.exports = {
|
|
436
|
+
preset: 'rnxsim/detox',
|
|
437
|
+
rootDir: __dirname,
|
|
438
|
+
testMatch: ['<rootDir>/e2e/**/*.test.ts', '<rootDir>/e2e/**/*.test.js'],
|
|
439
|
+
}
|
|
440
|
+
`,
|
|
441
|
+
)
|
|
442
|
+
console.log(` created ${configPath}`)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const e2eDir = resolve(cwd, 'e2e')
|
|
446
|
+
const samplePath = resolve(e2eDir, 'example.test.ts')
|
|
447
|
+
if (existsSync(samplePath)) {
|
|
448
|
+
console.log(` skip: ${samplePath} already exists`)
|
|
449
|
+
} else {
|
|
450
|
+
mkdirSync(e2eDir, { recursive: true })
|
|
451
|
+
writeFileSync(
|
|
452
|
+
samplePath,
|
|
453
|
+
`// sample rnx detox test. resolve \`detox\` via the jest preset in
|
|
454
|
+
// rnx-detox.config.cjs — no extra imports required.
|
|
455
|
+
|
|
456
|
+
import { by, device, element, expect, waitFor } from 'detox'
|
|
457
|
+
|
|
458
|
+
describe('example', () => {
|
|
459
|
+
beforeAll(async () => {
|
|
460
|
+
await device.launchApp()
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
it('shows the welcome text', async () => {
|
|
464
|
+
await waitFor(element(by.text('Welcome'))).toBeVisible().withTimeout(5000)
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
it('taps a button by id', async () => {
|
|
468
|
+
await element(by.id('start-button')).tap()
|
|
469
|
+
await expect(element(by.id('home-screen'))).toBeVisible()
|
|
470
|
+
})
|
|
471
|
+
})
|
|
472
|
+
`,
|
|
473
|
+
)
|
|
474
|
+
console.log(` created ${samplePath}`)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
console.log(`\n next:`)
|
|
478
|
+
console.log(` rnx detox # run the sample`)
|
|
479
|
+
console.log(` rnx detox --watch`)
|
|
480
|
+
rnxExit(0)
|
|
481
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
devices,
|
|
3
|
+
getDevice,
|
|
4
|
+
listSelectableDeviceModels,
|
|
5
|
+
type DeviceModel,
|
|
6
|
+
} from 'sootsim-engine/settings'
|
|
7
|
+
import { rnxExit } from '../run-rnx'
|
|
8
|
+
import {
|
|
9
|
+
callInBridge,
|
|
10
|
+
createBridgeFromParsed,
|
|
11
|
+
parseBridgeCliArgs,
|
|
12
|
+
type ParsedBridgeCliArgs,
|
|
13
|
+
} from '../ws-bridge'
|
|
14
|
+
|
|
15
|
+
interface DeviceCommandOptions {
|
|
16
|
+
port?: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type CurrentDeviceSnapshot = {
|
|
20
|
+
model: string
|
|
21
|
+
width: number
|
|
22
|
+
height: number
|
|
23
|
+
scale: number
|
|
24
|
+
name: string
|
|
25
|
+
} | null
|
|
26
|
+
|
|
27
|
+
function usage() {
|
|
28
|
+
console.log(`
|
|
29
|
+
rnx device — inspect or change the live device preset for a sim
|
|
30
|
+
|
|
31
|
+
usage:
|
|
32
|
+
rnx device
|
|
33
|
+
rnx device get
|
|
34
|
+
rnx device set <model> [--sim <id>]
|
|
35
|
+
rnx device list
|
|
36
|
+
|
|
37
|
+
examples:
|
|
38
|
+
rnx device
|
|
39
|
+
rnx device set iphone-16
|
|
40
|
+
rnx device set iphone-16-pro-max --sim a7
|
|
41
|
+
`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function printDevice(model: DeviceModel) {
|
|
45
|
+
const spec = getDevice(model)
|
|
46
|
+
console.log(
|
|
47
|
+
` ${model.padEnd(14)} ${String(spec.width).padStart(4)}x${String(spec.height).padEnd(4)} pt ${String(spec.width * spec.scale).padStart(4)}x${String(spec.height * spec.scale).padEnd(4)} px ${spec.name}`,
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function readCurrentDevice(
|
|
52
|
+
parsed: ParsedBridgeCliArgs,
|
|
53
|
+
): Promise<CurrentDeviceSnapshot> {
|
|
54
|
+
const bridge = createBridgeFromParsed(parsed)
|
|
55
|
+
try {
|
|
56
|
+
const settings = (await callInBridge<Record<string, unknown> | null>(
|
|
57
|
+
bridge,
|
|
58
|
+
'SootSim.bridges.settings.get',
|
|
59
|
+
)) ?? { deviceModel: null }
|
|
60
|
+
const model = typeof settings.deviceModel === 'string' ? settings.deviceModel : null
|
|
61
|
+
if (!model || !(model in devices)) return null
|
|
62
|
+
const spec = getDevice(model as DeviceModel)
|
|
63
|
+
return {
|
|
64
|
+
model,
|
|
65
|
+
width: spec.width,
|
|
66
|
+
height: spec.height,
|
|
67
|
+
scale: spec.scale,
|
|
68
|
+
name: spec.name,
|
|
69
|
+
}
|
|
70
|
+
} finally {
|
|
71
|
+
bridge.close()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function runDeviceCommand(args: string[], opts: DeviceCommandOptions) {
|
|
76
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
77
|
+
usage()
|
|
78
|
+
rnxExit(0)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const subcommand = args[0]
|
|
82
|
+
if (!subcommand || subcommand === 'get') {
|
|
83
|
+
const parsed = parseBridgeCliArgs(args, { port: opts.port })
|
|
84
|
+
const current = await readCurrentDevice(parsed)
|
|
85
|
+
if (!current) {
|
|
86
|
+
console.error(' error: could not read current device from the target sim')
|
|
87
|
+
rnxExit(1)
|
|
88
|
+
}
|
|
89
|
+
console.log(
|
|
90
|
+
` ${current.model} ${current.width}x${current.height} pt ${current.width * current.scale}x${current.height * current.scale} px ${current.name}`,
|
|
91
|
+
)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (subcommand === 'list') {
|
|
96
|
+
console.log(' available devices:\n')
|
|
97
|
+
for (const model of listSelectableDeviceModels()) printDevice(model)
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (subcommand === 'set') {
|
|
102
|
+
const model = args[1]
|
|
103
|
+
if (!model) {
|
|
104
|
+
console.error(' error: device set expects a model')
|
|
105
|
+
rnxExit(1)
|
|
106
|
+
}
|
|
107
|
+
if (!(model in devices)) {
|
|
108
|
+
console.error(` error: unknown device "${model}"`)
|
|
109
|
+
console.error(' run `rnx device list` to see valid models')
|
|
110
|
+
rnxExit(1)
|
|
111
|
+
}
|
|
112
|
+
const parsed = parseBridgeCliArgs(args, { port: opts.port })
|
|
113
|
+
const bridge = createBridgeFromParsed(parsed)
|
|
114
|
+
try {
|
|
115
|
+
const changed = await callInBridge<boolean>(
|
|
116
|
+
bridge,
|
|
117
|
+
'SootSim.bridges.settings.set',
|
|
118
|
+
'deviceModel',
|
|
119
|
+
model,
|
|
120
|
+
)
|
|
121
|
+
const spec = getDevice(model as DeviceModel)
|
|
122
|
+
console.log(
|
|
123
|
+
` ${changed ? 'set' : 'kept'} device: ${model} (${spec.width}x${spec.height} pt, ${spec.width * spec.scale}x${spec.height * spec.scale} px)${changed ? ' — reloading sim' : ''}`,
|
|
124
|
+
)
|
|
125
|
+
} finally {
|
|
126
|
+
bridge.close()
|
|
127
|
+
}
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
usage()
|
|
132
|
+
console.error(` unknown subcommand: ${subcommand}`)
|
|
133
|
+
rnxExit(1)
|
|
134
|
+
}
|