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,127 @@
|
|
|
1
|
+
import { runScreenshotsPlan } from '../../src/screenshots/orchestrate'
|
|
2
|
+
import { fetchOrgDeckFrames, type OrgDeckFrame } from '../../src/screenshots/org-deck'
|
|
3
|
+
import { authHeaderValue, resolveCliAuth } from '../auth'
|
|
4
|
+
|
|
5
|
+
interface RunScreenshotsOptions {
|
|
6
|
+
port?: number
|
|
7
|
+
verbose?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function runScreenshots(
|
|
11
|
+
args: string[],
|
|
12
|
+
_opts: RunScreenshotsOptions = {},
|
|
13
|
+
): Promise<number> {
|
|
14
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
15
|
+
console.log(`
|
|
16
|
+
rnx screenshot appstore — capture and compose app-store screenshot sets
|
|
17
|
+
|
|
18
|
+
usage:
|
|
19
|
+
rnx screenshot appstore --plan <plan.yaml> [options]
|
|
20
|
+
|
|
21
|
+
options:
|
|
22
|
+
--plan <path> screenshot plan yaml
|
|
23
|
+
--sim <id> reuse a live sim for capture
|
|
24
|
+
--app <target> override plan app target (port, url, or shell url)
|
|
25
|
+
--device <model> override plan capture device for this run
|
|
26
|
+
--org <slug> overlay copy/colors from a team's org screenshot deck
|
|
27
|
+
--repo <name> repo for --org (both required to pull the org deck)
|
|
28
|
+
--api-origin <url> contrast origin to fetch the deck from (default: the
|
|
29
|
+
signed-in session origin, else https://contrast.dev)
|
|
30
|
+
--capture-only stop after raw/framed intermediates
|
|
31
|
+
--compose-only skip flow capture, reuse existing raw screenshots
|
|
32
|
+
|
|
33
|
+
examples:
|
|
34
|
+
rnx screenshot appstore --plan app-store.yaml
|
|
35
|
+
rnx screenshot appstore --plan app-store.yaml --sim a9
|
|
36
|
+
rnx screenshot appstore --plan app-store.yaml --org acme --repo mobile
|
|
37
|
+
`)
|
|
38
|
+
return 0
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const getFlag = (name: string) => args.find((_, i) => args[i - 1] === name)
|
|
42
|
+
const consumedValueFlags = new Set([
|
|
43
|
+
'--plan',
|
|
44
|
+
'--sim',
|
|
45
|
+
'--app',
|
|
46
|
+
'--device',
|
|
47
|
+
'--org',
|
|
48
|
+
'--repo',
|
|
49
|
+
'--api-origin',
|
|
50
|
+
])
|
|
51
|
+
const positional = args.filter((arg, index) => {
|
|
52
|
+
if (arg.startsWith('-')) return false
|
|
53
|
+
const previous = args[index - 1]
|
|
54
|
+
return !previous || !consumedValueFlags.has(previous)
|
|
55
|
+
})
|
|
56
|
+
const planPath = getFlag('--plan') || positional[0]
|
|
57
|
+
if (!planPath) {
|
|
58
|
+
console.error(' error: screenshots plan path is required (`--plan <path>`).')
|
|
59
|
+
return 1
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const simId = getFlag('--sim')?.trim() || null
|
|
63
|
+
const appTarget = getFlag('--app')?.trim() || null
|
|
64
|
+
const deviceModel = getFlag('--device')?.trim() || null
|
|
65
|
+
const org = getFlag('--org')?.trim() || null
|
|
66
|
+
const repo = getFlag('--repo')?.trim() || null
|
|
67
|
+
const apiOrigin = getFlag('--api-origin')?.trim() || null
|
|
68
|
+
const captureOnly = args.includes('--capture-only')
|
|
69
|
+
const composeOnly = args.includes('--compose-only')
|
|
70
|
+
|
|
71
|
+
let orgDeckFrames: OrgDeckFrame[] | null = null
|
|
72
|
+
if (org || repo) {
|
|
73
|
+
if (!org || !repo) {
|
|
74
|
+
console.error(' error: --org and --repo must be passed together.')
|
|
75
|
+
return 1
|
|
76
|
+
}
|
|
77
|
+
const auth = resolveCliAuth()
|
|
78
|
+
if (!auth) {
|
|
79
|
+
console.error(
|
|
80
|
+
' error: --org/--repo needs auth — run `rnx login` or set RNX_API_KEY.',
|
|
81
|
+
)
|
|
82
|
+
return 1
|
|
83
|
+
}
|
|
84
|
+
const origin =
|
|
85
|
+
apiOrigin || (auth.kind === 'session' ? auth.origin : 'https://contrast.dev')
|
|
86
|
+
try {
|
|
87
|
+
orgDeckFrames = await fetchOrgDeckFrames({
|
|
88
|
+
org,
|
|
89
|
+
repo,
|
|
90
|
+
origin,
|
|
91
|
+
authHeader: authHeaderValue(auth),
|
|
92
|
+
})
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error(` ${(err as Error).message}`)
|
|
95
|
+
return 1
|
|
96
|
+
}
|
|
97
|
+
if (!orgDeckFrames || orgDeckFrames.length === 0) {
|
|
98
|
+
console.log(
|
|
99
|
+
` note: no org deck for ${org}/${repo} — composing with the plan's copy/colors.`,
|
|
100
|
+
)
|
|
101
|
+
} else {
|
|
102
|
+
console.log(` org deck: ${orgDeckFrames.length} slide(s) from ${org}/${repo}`)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const result = await runScreenshotsPlan(planPath, {
|
|
108
|
+
simId,
|
|
109
|
+
appTarget,
|
|
110
|
+
deviceModel,
|
|
111
|
+
captureOnly,
|
|
112
|
+
composeOnly,
|
|
113
|
+
orgDeckFrames,
|
|
114
|
+
})
|
|
115
|
+
console.log(` capture manifest: ${result.capture.manifestPath}`)
|
|
116
|
+
if (result.compose) {
|
|
117
|
+
console.log(` compose manifest: ${result.compose.manifestPath}`)
|
|
118
|
+
console.log(` exports: ${result.compose.outputs.length}`)
|
|
119
|
+
} else {
|
|
120
|
+
console.log(' compose skipped')
|
|
121
|
+
}
|
|
122
|
+
return 0
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.error(` screenshots failed: ${(err as Error).message}`)
|
|
125
|
+
return 1
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// rnx serve — foreground bridge host
|
|
2
|
+
//
|
|
3
|
+
// hosts the WS bridge + runtime HTTP server on port 7668 (or --port) so
|
|
4
|
+
// CLI commands and electron windows both work against one bridge. sims
|
|
5
|
+
// connect over ws to register themselves; cli clients connect to drive
|
|
6
|
+
// them; electron fetches renderer assets over http from the same port.
|
|
7
|
+
//
|
|
8
|
+
// run in the foreground (ctrl-c to stop). the persistent launchd/systemd
|
|
9
|
+
// daemon installation is handled by `rnx daemon install`.
|
|
10
|
+
|
|
11
|
+
import { DEFAULT_SOOTSIM_BRIDGE_PORT } from '../../src/bridge-constants'
|
|
12
|
+
import {
|
|
13
|
+
ensureRnxHome,
|
|
14
|
+
isDaemonLockfileFresh,
|
|
15
|
+
readDaemonLockfile,
|
|
16
|
+
} from '../../src/home-paths'
|
|
17
|
+
import { SootSimBridgeHost } from '../../src/host/bridge-host'
|
|
18
|
+
import { rnxExit } from '../run-rnx'
|
|
19
|
+
import { resolveDefaultUploadOrigin } from './upload'
|
|
20
|
+
|
|
21
|
+
interface ServeOptions {
|
|
22
|
+
port?: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function runServe(args: string[], opts: ServeOptions = {}) {
|
|
26
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
27
|
+
console.log(`
|
|
28
|
+
rnx serve — run the rnx bridge in the foreground
|
|
29
|
+
|
|
30
|
+
hosts the WS bridge that CLI commands talk to. once running, any rnx
|
|
31
|
+
renderer (browser, electron, headless playwright) that connects to port 7668
|
|
32
|
+
becomes drivable from 'rnx describe', 'rnx do tap', etc.
|
|
33
|
+
|
|
34
|
+
usage:
|
|
35
|
+
rnx serve [options]
|
|
36
|
+
|
|
37
|
+
options:
|
|
38
|
+
--port <n> bridge port (defaults to ${DEFAULT_SOOTSIM_BRIDGE_PORT})
|
|
39
|
+
--quiet suppress per-connection logging
|
|
40
|
+
|
|
41
|
+
examples:
|
|
42
|
+
rnx serve
|
|
43
|
+
rnx serve --port 7668 --quiet
|
|
44
|
+
`)
|
|
45
|
+
rnxExit(0)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const portArgIdx = args.indexOf('--port')
|
|
49
|
+
const port =
|
|
50
|
+
portArgIdx >= 0 && args[portArgIdx + 1]
|
|
51
|
+
? Number(args[portArgIdx + 1])
|
|
52
|
+
: (opts.port ?? DEFAULT_SOOTSIM_BRIDGE_PORT)
|
|
53
|
+
if (Number.isNaN(port)) {
|
|
54
|
+
console.error(` invalid --port value: ${args[portArgIdx + 1]}`)
|
|
55
|
+
rnxExit(1)
|
|
56
|
+
}
|
|
57
|
+
const quiet = args.includes('--quiet') || args.includes('-q')
|
|
58
|
+
|
|
59
|
+
// only one bridge per rnx home. refuse to start a second one
|
|
60
|
+
// regardless of requested port, since both would try to write the same
|
|
61
|
+
// daemon.json and electron/cli clients only read one lockfile.
|
|
62
|
+
const existingLock = readDaemonLockfile()
|
|
63
|
+
if (existingLock && isDaemonLockfileFresh(existingLock)) {
|
|
64
|
+
console.error(
|
|
65
|
+
` an rnx bridge is already running (pid ${existingLock.pid}, port ${existingLock.bridgePort})`,
|
|
66
|
+
)
|
|
67
|
+
console.error(` stop it with 'rnx daemon stop' first`)
|
|
68
|
+
rnxExit(1)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
ensureRnxHome()
|
|
72
|
+
// use the explicit upload/auth origin when configured, otherwise production.
|
|
73
|
+
// never probe a local Contrast stack during bridge startup.
|
|
74
|
+
const contrastOrigin = await resolveDefaultUploadOrigin()
|
|
75
|
+
const host = new SootSimBridgeHost({
|
|
76
|
+
port,
|
|
77
|
+
writeLockfile: true,
|
|
78
|
+
contrastOrigin,
|
|
79
|
+
})
|
|
80
|
+
const boundPort = await host.startAsync({ silent: quiet })
|
|
81
|
+
|
|
82
|
+
const started = Date.now()
|
|
83
|
+
const log = (line: string) => {
|
|
84
|
+
if (quiet) return
|
|
85
|
+
process.stdout.write(`${line}\n`)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const lastSimIds = new Set<string>()
|
|
89
|
+
const tickInterval = setInterval(() => {
|
|
90
|
+
const sims = host.listSims()
|
|
91
|
+
const currentIds = new Set(sims.map((b) => b.id))
|
|
92
|
+
for (const b of sims) {
|
|
93
|
+
if (!lastSimIds.has(b.id)) {
|
|
94
|
+
const label = b.title || b.url || b.origin || '(unknown)'
|
|
95
|
+
log(` + ${b.id} ${label}`)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const id of lastSimIds) {
|
|
99
|
+
if (!currentIds.has(id)) log(` - ${id}`)
|
|
100
|
+
}
|
|
101
|
+
lastSimIds.clear()
|
|
102
|
+
for (const id of currentIds) lastSimIds.add(id)
|
|
103
|
+
}, 500)
|
|
104
|
+
|
|
105
|
+
log(`rnx bridge listening on ws://localhost:${boundPort} (runtime http on same port)`)
|
|
106
|
+
if (boundPort !== port) {
|
|
107
|
+
log(` (preferred port ${port} was taken — fell back to ${boundPort})`)
|
|
108
|
+
}
|
|
109
|
+
log(` ready for browser, electron, or headless playwright sims to connect`)
|
|
110
|
+
log(` (ctrl-c to stop)`)
|
|
111
|
+
|
|
112
|
+
const shutdown = async (signal: NodeJS.Signals) => {
|
|
113
|
+
clearInterval(tickInterval)
|
|
114
|
+
log(
|
|
115
|
+
`\n ${signal} received — shutting down after ${Math.round((Date.now() - started) / 1000)}s`,
|
|
116
|
+
)
|
|
117
|
+
try {
|
|
118
|
+
await host.close()
|
|
119
|
+
} catch {}
|
|
120
|
+
rnxExit(0)
|
|
121
|
+
}
|
|
122
|
+
process.on('SIGINT', () => shutdown('SIGINT'))
|
|
123
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'))
|
|
124
|
+
// closing the terminal that spawned us sends SIGHUP — treat it like a
|
|
125
|
+
// clean shutdown so the lockfile doesn't linger.
|
|
126
|
+
process.on('SIGHUP', () => shutdown('SIGHUP'))
|
|
127
|
+
// last-ditch lockfile cleanup on any synchronous exit path. the
|
|
128
|
+
// close() call above is async so we can't await it here, but removing
|
|
129
|
+
// the lockfile is cheap + idempotent.
|
|
130
|
+
process.on('exit', () => {
|
|
131
|
+
try {
|
|
132
|
+
host.removeLockfile()
|
|
133
|
+
} catch {}
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// keep the event loop alive even if ws/http servers all close — we want
|
|
137
|
+
// the process to live until an explicit signal.
|
|
138
|
+
await new Promise<never>(() => {})
|
|
139
|
+
}
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
// rnx setup owns the optional machine and repository choices. rnx itself
|
|
2
|
+
// works without project setup; bare first-run onboarding launches ConnectRN
|
|
3
|
+
// during this flow and leaves the repository choice until the end.
|
|
4
|
+
|
|
5
|
+
import os from 'node:os'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import { DEFAULT_SOOTSIM_BRIDGE_PORT } from '../../src/bridge-constants'
|
|
8
|
+
import { getCliVersion } from '../../src/cli-version'
|
|
9
|
+
import {
|
|
10
|
+
readActiveRuntime,
|
|
11
|
+
readPrivacyPreferences,
|
|
12
|
+
readSimulatorDriverPreference,
|
|
13
|
+
runtimeDir,
|
|
14
|
+
type SimulatorDriverPreference,
|
|
15
|
+
writeOnboardingPreferences,
|
|
16
|
+
writePrivacyPreferences,
|
|
17
|
+
} from '../../src/home-paths'
|
|
18
|
+
import { rnxRuntime } from '../../src/runtime-delivery'
|
|
19
|
+
import { findDesktopCompanion } from '../desktop-companion'
|
|
20
|
+
import { confirm, select } from '../prompt'
|
|
21
|
+
import {
|
|
22
|
+
applyRepositorySetup,
|
|
23
|
+
findRepositoryPackageRoot,
|
|
24
|
+
formatRepositoryOperations,
|
|
25
|
+
planRepositorySetup,
|
|
26
|
+
type RepositorySetupPlan,
|
|
27
|
+
} from '../setup-repository'
|
|
28
|
+
import { trackCliError } from '../telemetry'
|
|
29
|
+
import {
|
|
30
|
+
accent,
|
|
31
|
+
bold,
|
|
32
|
+
muted,
|
|
33
|
+
printBrand,
|
|
34
|
+
printPromptBlock,
|
|
35
|
+
success,
|
|
36
|
+
withSpinner,
|
|
37
|
+
} from '../ui'
|
|
38
|
+
import { runOpenCommand } from './control'
|
|
39
|
+
import { daemonInstall, getDaemonServiceStatus } from './daemon'
|
|
40
|
+
import { isAgentEnv } from './inspect/env'
|
|
41
|
+
import { runInstallDesktop } from './install-desktop'
|
|
42
|
+
|
|
43
|
+
interface SetupOptions {
|
|
44
|
+
appDir: string
|
|
45
|
+
ci: boolean
|
|
46
|
+
dryRun: boolean
|
|
47
|
+
json: boolean
|
|
48
|
+
yes: boolean
|
|
49
|
+
service: boolean | undefined
|
|
50
|
+
repository: boolean | undefined
|
|
51
|
+
productAnalytics: boolean | undefined
|
|
52
|
+
simulatorDriver: SimulatorDriverPreference | undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface SetupRunOptions {
|
|
56
|
+
launchConnectRN?: boolean
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface SetupResult {
|
|
60
|
+
runtimeVersion: string
|
|
61
|
+
serviceInstalled: boolean
|
|
62
|
+
repositoryPrepared: boolean
|
|
63
|
+
simulatorDriver: SimulatorDriverPreference
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function runSetup(
|
|
67
|
+
args: string[],
|
|
68
|
+
runOptions: SetupRunOptions = {},
|
|
69
|
+
): Promise<SetupResult | undefined> {
|
|
70
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
71
|
+
printHelp()
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const options = parseSetupOptions(args)
|
|
76
|
+
const launchConnectRN =
|
|
77
|
+
runOptions.launchConnectRN ??
|
|
78
|
+
(!options.ci &&
|
|
79
|
+
!options.yes &&
|
|
80
|
+
!options.json &&
|
|
81
|
+
!options.dryRun &&
|
|
82
|
+
process.stdin.isTTY)
|
|
83
|
+
try {
|
|
84
|
+
printIntro(options)
|
|
85
|
+
|
|
86
|
+
const packageRoot = findRepositoryPackageRoot(options.appDir)
|
|
87
|
+
const serviceStatus = getDaemonServiceStatus()
|
|
88
|
+
const privacy = readPrivacyPreferences()
|
|
89
|
+
const service = await decideService(options, serviceStatus)
|
|
90
|
+
if (options.dryRun && service && !serviceStatus.installed && !options.json) {
|
|
91
|
+
console.log(` ${muted('Would enable')} ${accent('the background service')}`)
|
|
92
|
+
}
|
|
93
|
+
if (!service && !serviceStatus.installed && !options.json) {
|
|
94
|
+
console.log(` ${muted('Enable it later with')} ${accent('rnx daemon install')}`)
|
|
95
|
+
}
|
|
96
|
+
const simulatorDriver = await decideSimulatorDriver(options)
|
|
97
|
+
if (options.dryRun && !options.json) {
|
|
98
|
+
console.log(
|
|
99
|
+
` ${muted('Would use')} ${accent(simulatorDriver === 'playwright' ? 'Browser' : 'Desktop')}`,
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const manifest = options.json
|
|
104
|
+
? await rnxRuntime.fetchManifest()
|
|
105
|
+
: await withSpinner('Checking the rnx runtime', () => rnxRuntime.fetchManifest())
|
|
106
|
+
const runtimeVersion = rnxRuntime.resolveVersion(manifest, {
|
|
107
|
+
channel: 'stable',
|
|
108
|
+
}).version
|
|
109
|
+
const cliVersion = getCliVersion()
|
|
110
|
+
if (cliVersion === '0.0.0') {
|
|
111
|
+
throw new Error('could not resolve the installed rnx CLI version')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let runtimeInstalled = false
|
|
115
|
+
if (!options.dryRun) {
|
|
116
|
+
const activeRuntime = readActiveRuntime()
|
|
117
|
+
const install = () =>
|
|
118
|
+
rnxRuntime.install({
|
|
119
|
+
version: runtimeVersion,
|
|
120
|
+
channel: 'stable',
|
|
121
|
+
setActive: true,
|
|
122
|
+
protectVersions: activeRuntime ? [activeRuntime] : [],
|
|
123
|
+
})
|
|
124
|
+
const result = options.json
|
|
125
|
+
? await install()
|
|
126
|
+
: await withSpinner(`Preparing runtime ${runtimeVersion}`, install)
|
|
127
|
+
runtimeInstalled = result.installed
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let serviceInstalled = serviceStatus.installed
|
|
131
|
+
if (service && !serviceStatus.installed && !options.dryRun) {
|
|
132
|
+
if (options.json) {
|
|
133
|
+
await daemonInstall({ port: DEFAULT_SOOTSIM_BRIDGE_PORT, force: false })
|
|
134
|
+
} else {
|
|
135
|
+
console.log(`\n ${accent('◆')} ${bold('Enabling the background service')}`)
|
|
136
|
+
await daemonInstall({ port: DEFAULT_SOOTSIM_BRIDGE_PORT, force: false })
|
|
137
|
+
}
|
|
138
|
+
serviceInstalled = true
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (simulatorDriver === 'electron' && !options.dryRun && !findDesktopCompanion()) {
|
|
142
|
+
await runInstallDesktop(['--yes'])
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (launchConnectRN) {
|
|
146
|
+
console.log(`\n ${accent('◆')} Opening ConnectRN`)
|
|
147
|
+
await runOpenCommand([
|
|
148
|
+
'ConnectRN',
|
|
149
|
+
'--driver',
|
|
150
|
+
simulatorDriver,
|
|
151
|
+
'--no-describe',
|
|
152
|
+
'--quiet',
|
|
153
|
+
])
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const productAnalytics = await decidePrivacy(options, privacy)
|
|
157
|
+
const crashReports = false
|
|
158
|
+
|
|
159
|
+
const repositoryPlan = packageRoot
|
|
160
|
+
? planRepositorySetup({ appDir: packageRoot, cliVersion })
|
|
161
|
+
: null
|
|
162
|
+
const repository = await decideRepository(options, repositoryPlan)
|
|
163
|
+
if (repository && repositoryPlan) {
|
|
164
|
+
if (!options.json) {
|
|
165
|
+
for (const operation of formatRepositoryOperations(repositoryPlan)) {
|
|
166
|
+
console.log(` ${muted(options.dryRun ? 'Would run' : 'Running')} ${operation}`)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
await applyRepositorySetup(repositoryPlan, {
|
|
170
|
+
dryRun: options.dryRun,
|
|
171
|
+
json: options.json,
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!options.dryRun) {
|
|
176
|
+
writePrivacyPreferences({ productAnalytics, crashReports })
|
|
177
|
+
writeOnboardingPreferences(simulatorDriver)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const result: SetupResult = {
|
|
181
|
+
runtimeVersion,
|
|
182
|
+
serviceInstalled,
|
|
183
|
+
repositoryPrepared: repository,
|
|
184
|
+
simulatorDriver,
|
|
185
|
+
}
|
|
186
|
+
if (options.json) {
|
|
187
|
+
console.log(
|
|
188
|
+
JSON.stringify(
|
|
189
|
+
{
|
|
190
|
+
...result,
|
|
191
|
+
runtimeInstalled,
|
|
192
|
+
dryRun: options.dryRun,
|
|
193
|
+
cache: runtimeCacheMetadata(runtimeVersion),
|
|
194
|
+
repositoryOperations:
|
|
195
|
+
repository && repositoryPlan
|
|
196
|
+
? formatRepositoryOperations(repositoryPlan)
|
|
197
|
+
: [],
|
|
198
|
+
},
|
|
199
|
+
null,
|
|
200
|
+
2,
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
} else {
|
|
204
|
+
console.log(
|
|
205
|
+
options.dryRun
|
|
206
|
+
? `\n ${success('✓')} Dry run complete.`
|
|
207
|
+
: `\n ${success('✓')} ${launchConnectRN ? 'ConnectRN is open. ' : ''}rnx is ready.`,
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return result
|
|
212
|
+
} catch (error) {
|
|
213
|
+
trackCliError(error)
|
|
214
|
+
throw error
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function parseSetupOptions(args: string[]): SetupOptions {
|
|
219
|
+
const appIndex = args.indexOf('--app')
|
|
220
|
+
const appArg = appIndex >= 0 ? args[appIndex + 1] : undefined
|
|
221
|
+
if (appIndex >= 0 && (!appArg || appArg.startsWith('-'))) {
|
|
222
|
+
throw new Error('--app requires a directory')
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
appDir: path.resolve(appArg ?? process.cwd()),
|
|
226
|
+
ci: args.includes('--ci') || isAgentEnv(),
|
|
227
|
+
dryRun: args.includes('--dry-run'),
|
|
228
|
+
json: args.includes('--json'),
|
|
229
|
+
yes: args.includes('--yes') || args.includes('-y'),
|
|
230
|
+
service: readBooleanFlag(args, '--service', '--no-service'),
|
|
231
|
+
repository: readBooleanFlag(args, '--repo', '--no-repo'),
|
|
232
|
+
productAnalytics: readOnOffFlag(args, '--analytics'),
|
|
233
|
+
simulatorDriver: readSimulatorDriverFlag(args),
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function readSimulatorDriverFlag(args: string[]): SimulatorDriverPreference | undefined {
|
|
238
|
+
const desktop = args.includes('--desktop')
|
|
239
|
+
const browser = args.includes('--browser')
|
|
240
|
+
if (desktop && browser) {
|
|
241
|
+
throw new Error('--desktop and --browser cannot be used together')
|
|
242
|
+
}
|
|
243
|
+
if (desktop) return 'electron'
|
|
244
|
+
if (browser) return 'playwright'
|
|
245
|
+
return undefined
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function readBooleanFlag(
|
|
249
|
+
args: string[],
|
|
250
|
+
positive: string,
|
|
251
|
+
negative: string,
|
|
252
|
+
): boolean | undefined {
|
|
253
|
+
if (args.includes(positive) && args.includes(negative)) {
|
|
254
|
+
throw new Error(`${positive} and ${negative} cannot be used together`)
|
|
255
|
+
}
|
|
256
|
+
if (args.includes(positive)) return true
|
|
257
|
+
if (args.includes(negative)) return false
|
|
258
|
+
return undefined
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function readOnOffFlag(args: string[], name: string): boolean | undefined {
|
|
262
|
+
const equals = args.find((arg) => arg.startsWith(`${name}=`))
|
|
263
|
+
const index = args.indexOf(name)
|
|
264
|
+
const raw = equals?.slice(name.length + 1) ?? (index >= 0 ? args[index + 1] : undefined)
|
|
265
|
+
if (raw === undefined) return undefined
|
|
266
|
+
if (raw === 'on' || raw === 'yes' || raw === 'true') return true
|
|
267
|
+
if (raw === 'off' || raw === 'no' || raw === 'false') return false
|
|
268
|
+
throw new Error(`${name} must be on or off`)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function decideService(
|
|
272
|
+
options: SetupOptions,
|
|
273
|
+
status: ReturnType<typeof getDaemonServiceStatus>,
|
|
274
|
+
): Promise<boolean> {
|
|
275
|
+
if (options.ci) {
|
|
276
|
+
if (options.service === true) throw new Error('--ci cannot install a machine service')
|
|
277
|
+
return false
|
|
278
|
+
}
|
|
279
|
+
if (!status.supported) {
|
|
280
|
+
if (!options.json) {
|
|
281
|
+
printPromptBlock('Background service', [
|
|
282
|
+
'The persistent service is currently available on macOS and Linux.',
|
|
283
|
+
'rnx will start the bridge when needed on this platform.',
|
|
284
|
+
])
|
|
285
|
+
}
|
|
286
|
+
return false
|
|
287
|
+
}
|
|
288
|
+
if (status.installed && options.service === undefined) {
|
|
289
|
+
if (!options.json) console.log(` ${success('✓')} Background service is ready`)
|
|
290
|
+
return true
|
|
291
|
+
}
|
|
292
|
+
if (options.service !== undefined) return options.service
|
|
293
|
+
if (options.yes || options.dryRun) return true
|
|
294
|
+
requireInteractive(options, '--service or --no-service')
|
|
295
|
+
printPromptBlock('Background service', [
|
|
296
|
+
'Keeps the local bridge and runtime ready between commands.',
|
|
297
|
+
'Agent inspection, interaction, and test commands start much faster.',
|
|
298
|
+
])
|
|
299
|
+
return confirm('Enable the background service?', true)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function decideSimulatorDriver(
|
|
303
|
+
options: SetupOptions,
|
|
304
|
+
): Promise<SimulatorDriverPreference> {
|
|
305
|
+
if (options.simulatorDriver) return options.simulatorDriver
|
|
306
|
+
const current = readSimulatorDriverPreference()
|
|
307
|
+
if (current) return current
|
|
308
|
+
if (options.ci || options.yes || options.dryRun) return 'playwright'
|
|
309
|
+
requireInteractive(options, '--desktop or --browser')
|
|
310
|
+
const desktopDescription =
|
|
311
|
+
process.platform === 'darwin'
|
|
312
|
+
? 'Desktop downloads a DMG and installs rnx.app.'
|
|
313
|
+
: process.platform === 'win32'
|
|
314
|
+
? 'Desktop downloads and runs the rnx installer.'
|
|
315
|
+
: 'Desktop downloads the matching rnx AppImage.'
|
|
316
|
+
const desktopChoice =
|
|
317
|
+
process.platform === 'darwin'
|
|
318
|
+
? 'Desktop app Installs rnx.app from a DMG'
|
|
319
|
+
: process.platform === 'win32'
|
|
320
|
+
? 'Desktop app Runs the rnx installer'
|
|
321
|
+
: 'Desktop app Installs the matching AppImage'
|
|
322
|
+
printPromptBlock('Simulator window', [
|
|
323
|
+
'Browser opens immediately in an isolated Chrome window.',
|
|
324
|
+
desktopDescription,
|
|
325
|
+
'The Desktop app opens in a dedicated native window.',
|
|
326
|
+
])
|
|
327
|
+
const choice = await select('Where should rnx open?', [
|
|
328
|
+
'Browser (recommended) No additional download',
|
|
329
|
+
desktopChoice,
|
|
330
|
+
])
|
|
331
|
+
return choice === 0 ? 'playwright' : 'electron'
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function decidePrivacy(
|
|
335
|
+
options: SetupOptions,
|
|
336
|
+
current: ReturnType<typeof readPrivacyPreferences>,
|
|
337
|
+
): Promise<boolean> {
|
|
338
|
+
if (options.productAnalytics !== undefined) return options.productAnalytics
|
|
339
|
+
if (current.configured) return current.productAnalytics
|
|
340
|
+
if (options.yes || options.ci || !process.stdin.isTTY) return false
|
|
341
|
+
printPromptBlock('Private by default', [
|
|
342
|
+
'Diagnostics stay off unless you opt in.',
|
|
343
|
+
'Reports can contain package compatibility or an error class.',
|
|
344
|
+
'They never contain your code or app data.',
|
|
345
|
+
])
|
|
346
|
+
return confirm('Share bounded diagnostics to improve rnx?', false)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function decideRepository(
|
|
350
|
+
options: SetupOptions,
|
|
351
|
+
plan: RepositorySetupPlan | null,
|
|
352
|
+
): Promise<boolean> {
|
|
353
|
+
if (!plan) {
|
|
354
|
+
if (options.repository === true) {
|
|
355
|
+
throw new Error('--repo requires a package.json inside a Git repository')
|
|
356
|
+
}
|
|
357
|
+
return false
|
|
358
|
+
}
|
|
359
|
+
if (plan.operations.length === 0) return true
|
|
360
|
+
if (options.repository !== undefined) return options.repository
|
|
361
|
+
if (options.yes || options.ci || !process.stdin.isTTY) return false
|
|
362
|
+
printPromptBlock('Share rnx with your team', [
|
|
363
|
+
'rnx already works here with no project setup.',
|
|
364
|
+
'This only adds an exact rnxsim dev dependency and an rnx script for teammates.',
|
|
365
|
+
])
|
|
366
|
+
return confirm(`Add rnx to ${path.basename(plan.appDir)}?`, false)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function requireInteractive(options: SetupOptions, flags: string): void {
|
|
370
|
+
if (process.stdin.isTTY && !options.ci) return
|
|
371
|
+
throw new Error(`non-interactive setup requires ${flags}, or --yes to accept defaults`)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function runtimeCacheMetadata(runtimeVersion: string) {
|
|
375
|
+
const platform = os.platform()
|
|
376
|
+
const arch = os.arch()
|
|
377
|
+
return {
|
|
378
|
+
path: runtimeDir(runtimeVersion),
|
|
379
|
+
key: `rnx-runtime-${platform}-${arch}-${runtimeVersion}`,
|
|
380
|
+
inputs: { platform, arch, runtimeVersion },
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function printIntro(options: SetupOptions): void {
|
|
385
|
+
if (options.json) return
|
|
386
|
+
printBrand('Run any React Native app in a local simulator. No project setup required.')
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function printHelp(): void {
|
|
390
|
+
console.log(`
|
|
391
|
+
rnx setup - configure optional local conveniences
|
|
392
|
+
|
|
393
|
+
rnx works without project setup. This command can enable the faster background
|
|
394
|
+
service, choose Browser or Desktop, configure private diagnostics, and optionally
|
|
395
|
+
add an exact rnxsim dependency plus an rnx script for teammates.
|
|
396
|
+
|
|
397
|
+
usage:
|
|
398
|
+
rnx setup
|
|
399
|
+
rnx setup --yes
|
|
400
|
+
rnx setup --ci --yes --json
|
|
401
|
+
rnx setup --dry-run
|
|
402
|
+
|
|
403
|
+
choices:
|
|
404
|
+
--service | --no-service
|
|
405
|
+
--desktop | --browser
|
|
406
|
+
--analytics on|off
|
|
407
|
+
--repo | --no-repo
|
|
408
|
+
|
|
409
|
+
automation:
|
|
410
|
+
--yes accept recommended machine defaults; diagnostics and repo changes stay off
|
|
411
|
+
--ci never prompt or install a background service
|
|
412
|
+
--json emit runtime and cache metadata as JSON
|
|
413
|
+
--dry-run show choices and repository operations without changing anything
|
|
414
|
+
--app DIR inspect DIR for an optional repository installation
|
|
415
|
+
`)
|
|
416
|
+
}
|