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,542 @@
|
|
|
1
|
+
// `rnx assert <verb> <verb-args...> <assertion-flags...>`
|
|
2
|
+
//
|
|
3
|
+
// thin wrapper that runs any read verb with --json forced on, captures the
|
|
4
|
+
// JSON payload, and converts predicate flags into a bisect-friendly exit
|
|
5
|
+
// code. see docs for the full flag surface — this file intentionally stays
|
|
6
|
+
// small: every assertion is a pure function over the parsed JSON, and the
|
|
7
|
+
// only runtime work is the retry loop for --within and the optional shell
|
|
8
|
+
// out to `jq` for --jq.
|
|
9
|
+
//
|
|
10
|
+
// exit codes:
|
|
11
|
+
// 0 assertion passed (bisect: good)
|
|
12
|
+
// 1 assertion evaluated and failed (bisect: bad)
|
|
13
|
+
// 125 could not evaluate (bisect: skip)
|
|
14
|
+
// 2 misuse — bad flags, unknown verb (treated as bad by bisect)
|
|
15
|
+
|
|
16
|
+
import { spawn } from 'child_process'
|
|
17
|
+
import { rnxExit } from '../run-rnx'
|
|
18
|
+
import { rnxSelfInvocation } from '../self-invocation'
|
|
19
|
+
|
|
20
|
+
// READ_VERBS lists every command whose stdout we know is valid JSON under
|
|
21
|
+
// --json. keeping this explicit means `rnx assert tap 100 200` fails
|
|
22
|
+
// fast with a clear message instead of silently running a write verb and
|
|
23
|
+
// trying to parse " tapped 100,200" as JSON.
|
|
24
|
+
const READ_VERBS = new Set([
|
|
25
|
+
// top-level
|
|
26
|
+
'describe',
|
|
27
|
+
'find',
|
|
28
|
+
'list',
|
|
29
|
+
'network',
|
|
30
|
+
'logs',
|
|
31
|
+
// get <noun>
|
|
32
|
+
'count',
|
|
33
|
+
'tree',
|
|
34
|
+
'url',
|
|
35
|
+
'a11y',
|
|
36
|
+
'node',
|
|
37
|
+
'layout',
|
|
38
|
+
'keyboard',
|
|
39
|
+
'errors',
|
|
40
|
+
'warnings',
|
|
41
|
+
'requests',
|
|
42
|
+
'animations',
|
|
43
|
+
'animation',
|
|
44
|
+
'state',
|
|
45
|
+
'sample-color',
|
|
46
|
+
])
|
|
47
|
+
|
|
48
|
+
// assertion flags that assert recognizes. everything else is passed through
|
|
49
|
+
// to the inner verb untouched. keeping this as a set (rather than positional
|
|
50
|
+
// parsing) means `rnx assert find --testid X --count 4` reads naturally.
|
|
51
|
+
const BOOL_ASSERT_FLAGS = new Set([
|
|
52
|
+
'--exists',
|
|
53
|
+
'--empty',
|
|
54
|
+
'--negate',
|
|
55
|
+
'--quiet',
|
|
56
|
+
'--allow-timeout',
|
|
57
|
+
'!',
|
|
58
|
+
])
|
|
59
|
+
const VALUE_ASSERT_FLAGS = new Set([
|
|
60
|
+
'--count',
|
|
61
|
+
'--count-at-least',
|
|
62
|
+
'--count-at-most',
|
|
63
|
+
'--contains',
|
|
64
|
+
'--not-contains',
|
|
65
|
+
'--matches',
|
|
66
|
+
'--not-matches',
|
|
67
|
+
'--equals',
|
|
68
|
+
'--jq',
|
|
69
|
+
'--has-path',
|
|
70
|
+
'--within',
|
|
71
|
+
])
|
|
72
|
+
// --path-equals takes TWO values: <dotted.path> <value>
|
|
73
|
+
const DUAL_VALUE_ASSERT_FLAGS = new Set(['--path-equals'])
|
|
74
|
+
|
|
75
|
+
interface Predicate {
|
|
76
|
+
kind: string
|
|
77
|
+
args: string[]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface AssertArgs {
|
|
81
|
+
verbArgs: string[]
|
|
82
|
+
predicates: Predicate[]
|
|
83
|
+
withinMs: number
|
|
84
|
+
negate: boolean
|
|
85
|
+
quiet: boolean
|
|
86
|
+
allowTimeout: boolean
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseAssertArgs(raw: string[]): AssertArgs | { error: string } {
|
|
90
|
+
const verbArgs: string[] = []
|
|
91
|
+
const predicates: Predicate[] = []
|
|
92
|
+
let withinMs = 0
|
|
93
|
+
let negate = false
|
|
94
|
+
let quiet = false
|
|
95
|
+
let allowTimeout = false
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < raw.length; i++) {
|
|
98
|
+
const a = raw[i]
|
|
99
|
+
if (a === '--within') {
|
|
100
|
+
const v = Number(raw[i + 1])
|
|
101
|
+
if (!Number.isFinite(v) || v < 0) {
|
|
102
|
+
return { error: `--within requires a non-negative number, got: ${raw[i + 1]}` }
|
|
103
|
+
}
|
|
104
|
+
withinMs = v
|
|
105
|
+
i++
|
|
106
|
+
continue
|
|
107
|
+
}
|
|
108
|
+
if (a === '--negate' || a === '!') {
|
|
109
|
+
negate = true
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
if (a === '--quiet') {
|
|
113
|
+
quiet = true
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
if (a === '--allow-timeout') {
|
|
117
|
+
allowTimeout = true
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
if (BOOL_ASSERT_FLAGS.has(a)) {
|
|
121
|
+
predicates.push({ kind: a, args: [] })
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
if (VALUE_ASSERT_FLAGS.has(a)) {
|
|
125
|
+
const v = raw[i + 1]
|
|
126
|
+
if (v == null) return { error: `${a} requires a value` }
|
|
127
|
+
predicates.push({ kind: a, args: [v] })
|
|
128
|
+
i++
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
if (DUAL_VALUE_ASSERT_FLAGS.has(a)) {
|
|
132
|
+
const v1 = raw[i + 1]
|
|
133
|
+
const v2 = raw[i + 2]
|
|
134
|
+
if (v1 == null || v2 == null) return { error: `${a} requires <path> <value>` }
|
|
135
|
+
predicates.push({ kind: a, args: [v1, v2] })
|
|
136
|
+
i += 2
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
verbArgs.push(a)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (verbArgs.length === 0)
|
|
143
|
+
return { error: 'assert requires a verb (e.g. find, describe, get errors)' }
|
|
144
|
+
return { verbArgs, predicates, withinMs, negate, quiet, allowTimeout }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// resolve the verb name the user typed. handles `get <noun>`, `do <verb>`,
|
|
148
|
+
// and plain top-level verbs. returns null if the verb is unknown or is a
|
|
149
|
+
// known write verb (which would never produce parseable json).
|
|
150
|
+
function resolveReadVerb(verbArgs: string[]): string | null {
|
|
151
|
+
if (verbArgs[0] === 'get' || verbArgs[0] === 'debug') {
|
|
152
|
+
return READ_VERBS.has(verbArgs[1]) ? verbArgs[1] : null
|
|
153
|
+
}
|
|
154
|
+
if (verbArgs[0] === 'do' || verbArgs[0] === 'wait') return null
|
|
155
|
+
return READ_VERBS.has(verbArgs[0]) ? verbArgs[0] : null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface VerbResult {
|
|
159
|
+
ok: boolean
|
|
160
|
+
payload: unknown
|
|
161
|
+
reason?: string
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function runVerbOnce(verbArgs: string[]): Promise<VerbResult> {
|
|
165
|
+
// re-invoke self through the real CLI entry, so every code path the user's
|
|
166
|
+
// verb would take, assert takes too.
|
|
167
|
+
const { executable, prefixArgs } = rnxSelfInvocation()
|
|
168
|
+
const args = [...prefixArgs, ...verbArgs, '--json']
|
|
169
|
+
|
|
170
|
+
return new Promise<VerbResult>((resolve) => {
|
|
171
|
+
const proc = spawn(executable, args, {
|
|
172
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
173
|
+
env: process.env,
|
|
174
|
+
})
|
|
175
|
+
let stdout = ''
|
|
176
|
+
let stderr = ''
|
|
177
|
+
proc.stdout.on('data', (d) => (stdout += d.toString()))
|
|
178
|
+
proc.stderr.on('data', (d) => (stderr += d.toString()))
|
|
179
|
+
proc.on('error', (err) => {
|
|
180
|
+
resolve({ ok: false, payload: null, reason: `spawn failed: ${err.message}` })
|
|
181
|
+
})
|
|
182
|
+
proc.on('close', (code) => {
|
|
183
|
+
if (code !== 0) {
|
|
184
|
+
// stderr tail usually has the bridge diagnostic — include a short
|
|
185
|
+
// slice so the skip message is actionable.
|
|
186
|
+
const tail = stderr.trim().split('\n').slice(-2).join(' | ').slice(0, 200)
|
|
187
|
+
resolve({
|
|
188
|
+
ok: false,
|
|
189
|
+
payload: null,
|
|
190
|
+
reason: `verb exited ${code}${tail ? `: ${tail}` : ''}`,
|
|
191
|
+
})
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
if (!stdout.trim()) {
|
|
195
|
+
resolve({ ok: false, payload: null, reason: 'verb produced no output' })
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
const payload = JSON.parse(stdout)
|
|
200
|
+
resolve({ ok: true, payload })
|
|
201
|
+
} catch {
|
|
202
|
+
resolve({
|
|
203
|
+
ok: false,
|
|
204
|
+
payload: null,
|
|
205
|
+
reason: `verb output was not valid json: ${stdout.slice(0, 120)}`,
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// read a dotted path against a JSON value. numeric segments index arrays,
|
|
213
|
+
// anything else reads object keys. returns { found: false } for any missing
|
|
214
|
+
// segment so callers can distinguish "path exists, value is null" from
|
|
215
|
+
// "path doesn't exist."
|
|
216
|
+
function readPath(
|
|
217
|
+
value: unknown,
|
|
218
|
+
path: string,
|
|
219
|
+
): { found: true; value: unknown } | { found: false } {
|
|
220
|
+
const parts = path.split('.').filter((s) => s.length > 0)
|
|
221
|
+
let cur: unknown = value
|
|
222
|
+
for (const part of parts) {
|
|
223
|
+
if (cur == null) return { found: false }
|
|
224
|
+
if (Array.isArray(cur)) {
|
|
225
|
+
const idx = Number(part)
|
|
226
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= cur.length) return { found: false }
|
|
227
|
+
cur = cur[idx]
|
|
228
|
+
continue
|
|
229
|
+
}
|
|
230
|
+
if (typeof cur === 'object') {
|
|
231
|
+
const obj = cur as Record<string, unknown>
|
|
232
|
+
if (!(part in obj)) return { found: false }
|
|
233
|
+
cur = obj[part]
|
|
234
|
+
continue
|
|
235
|
+
}
|
|
236
|
+
return { found: false }
|
|
237
|
+
}
|
|
238
|
+
return { found: true, value: cur }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
interface PredicateResult {
|
|
242
|
+
ok: boolean
|
|
243
|
+
reason: string
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function lengthOf(payload: unknown): number | null {
|
|
247
|
+
if (Array.isArray(payload)) return payload.length
|
|
248
|
+
if (payload && typeof payload === 'object') return Object.keys(payload).length
|
|
249
|
+
return null
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function evalPredicate(
|
|
253
|
+
pred: Predicate,
|
|
254
|
+
payload: unknown,
|
|
255
|
+
): Promise<PredicateResult> {
|
|
256
|
+
const { kind, args } = pred
|
|
257
|
+
switch (kind) {
|
|
258
|
+
case '--exists': {
|
|
259
|
+
if (payload == null) return { ok: false, reason: 'payload is null' }
|
|
260
|
+
const len = lengthOf(payload)
|
|
261
|
+
if (len === 0) return { ok: false, reason: 'payload is empty' }
|
|
262
|
+
return { ok: true, reason: 'exists' }
|
|
263
|
+
}
|
|
264
|
+
case '--empty': {
|
|
265
|
+
if (payload == null) return { ok: true, reason: 'payload is null' }
|
|
266
|
+
const len = lengthOf(payload)
|
|
267
|
+
if (len === 0) return { ok: true, reason: 'payload is empty' }
|
|
268
|
+
return { ok: false, reason: `payload has ${len} item(s)` }
|
|
269
|
+
}
|
|
270
|
+
case '--count': {
|
|
271
|
+
const want = Number(args[0])
|
|
272
|
+
const got = lengthOf(payload)
|
|
273
|
+
if (got == null)
|
|
274
|
+
return { ok: false, reason: `payload not countable (${typeof payload})` }
|
|
275
|
+
return {
|
|
276
|
+
ok: got === want,
|
|
277
|
+
reason: `count=${got}, want=${want}`,
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
case '--count-at-least': {
|
|
281
|
+
const want = Number(args[0])
|
|
282
|
+
const got = lengthOf(payload)
|
|
283
|
+
if (got == null) return { ok: false, reason: `payload not countable` }
|
|
284
|
+
return { ok: got >= want, reason: `count=${got}, want>=${want}` }
|
|
285
|
+
}
|
|
286
|
+
case '--count-at-most': {
|
|
287
|
+
const want = Number(args[0])
|
|
288
|
+
const got = lengthOf(payload)
|
|
289
|
+
if (got == null) return { ok: false, reason: `payload not countable` }
|
|
290
|
+
return { ok: got <= want, reason: `count=${got}, want<=${want}` }
|
|
291
|
+
}
|
|
292
|
+
case '--contains': {
|
|
293
|
+
const needle = args[0]
|
|
294
|
+
const hay = JSON.stringify(payload)
|
|
295
|
+
return {
|
|
296
|
+
ok: hay.includes(needle),
|
|
297
|
+
reason: hay.includes(needle) ? `contains "${needle}"` : `missing "${needle}"`,
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
case '--not-contains': {
|
|
301
|
+
const needle = args[0]
|
|
302
|
+
const hay = JSON.stringify(payload)
|
|
303
|
+
return {
|
|
304
|
+
ok: !hay.includes(needle),
|
|
305
|
+
reason: !hay.includes(needle)
|
|
306
|
+
? `not contains "${needle}"`
|
|
307
|
+
: `unexpected "${needle}"`,
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
case '--matches': {
|
|
311
|
+
const re = new RegExp(args[0])
|
|
312
|
+
const hay = JSON.stringify(payload)
|
|
313
|
+
const ok = re.test(hay)
|
|
314
|
+
return {
|
|
315
|
+
ok,
|
|
316
|
+
reason: ok ? `matches /${args[0]}/` : `no match for /${args[0]}/`,
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
case '--not-matches': {
|
|
320
|
+
const re = new RegExp(args[0])
|
|
321
|
+
const hay = JSON.stringify(payload)
|
|
322
|
+
const ok = !re.test(hay)
|
|
323
|
+
return {
|
|
324
|
+
ok,
|
|
325
|
+
reason: ok ? `not matches /${args[0]}/` : `unexpected /${args[0]}/`,
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
case '--equals': {
|
|
329
|
+
const want = args[0]
|
|
330
|
+
const got =
|
|
331
|
+
typeof payload === 'string'
|
|
332
|
+
? payload
|
|
333
|
+
: typeof payload === 'number' || typeof payload === 'boolean'
|
|
334
|
+
? String(payload)
|
|
335
|
+
: JSON.stringify(payload)
|
|
336
|
+
return {
|
|
337
|
+
ok: got === want,
|
|
338
|
+
reason: `got=${JSON.stringify(got)}, want=${JSON.stringify(want)}`,
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
case '--has-path': {
|
|
342
|
+
const r = readPath(payload, args[0])
|
|
343
|
+
return {
|
|
344
|
+
ok: r.found,
|
|
345
|
+
reason: r.found ? `path ${args[0]} present` : `path ${args[0]} missing`,
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
case '--path-equals': {
|
|
349
|
+
const [path, want] = args
|
|
350
|
+
const r = readPath(payload, path)
|
|
351
|
+
if (!r.found) return { ok: false, reason: `path ${path} missing` }
|
|
352
|
+
const got =
|
|
353
|
+
typeof r.value === 'string' ||
|
|
354
|
+
typeof r.value === 'number' ||
|
|
355
|
+
typeof r.value === 'boolean'
|
|
356
|
+
? String(r.value)
|
|
357
|
+
: JSON.stringify(r.value)
|
|
358
|
+
return { ok: got === want, reason: `${path}=${got}, want=${want}` }
|
|
359
|
+
}
|
|
360
|
+
case '--jq': {
|
|
361
|
+
// shell out to `jq`. if jq isn't installed we can't evaluate, so we
|
|
362
|
+
// bubble that up as a 125-skip rather than treating it as a failure.
|
|
363
|
+
const expr = args[0]
|
|
364
|
+
try {
|
|
365
|
+
const out = await runJq(expr, payload)
|
|
366
|
+
const trimmed = out.trim()
|
|
367
|
+
const truthy =
|
|
368
|
+
trimmed.length > 0 &&
|
|
369
|
+
trimmed !== 'null' &&
|
|
370
|
+
trimmed !== 'false' &&
|
|
371
|
+
trimmed !== '""'
|
|
372
|
+
return { ok: truthy, reason: `jq: ${trimmed.slice(0, 120)}` }
|
|
373
|
+
} catch (err: any) {
|
|
374
|
+
// throw so the caller can turn this into a 125 — consistent with how
|
|
375
|
+
// "verb crashed" is treated. we don't want bisect marking commits bad
|
|
376
|
+
// because jq isn't installed.
|
|
377
|
+
throw new Error(`jq evaluation failed: ${err.message}`)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
default:
|
|
381
|
+
return { ok: false, reason: `unknown predicate ${kind}` }
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function runJq(expr: string, payload: unknown): Promise<string> {
|
|
386
|
+
return new Promise((resolve, reject) => {
|
|
387
|
+
const proc = spawn('jq', [expr], { stdio: ['pipe', 'pipe', 'pipe'] })
|
|
388
|
+
let out = ''
|
|
389
|
+
let err = ''
|
|
390
|
+
proc.stdout.on('data', (d) => (out += d.toString()))
|
|
391
|
+
proc.stderr.on('data', (d) => (err += d.toString()))
|
|
392
|
+
proc.on('error', reject)
|
|
393
|
+
proc.on('close', (code) => {
|
|
394
|
+
if (code !== 0) {
|
|
395
|
+
reject(new Error(err.trim() || `jq exited ${code}`))
|
|
396
|
+
return
|
|
397
|
+
}
|
|
398
|
+
resolve(out)
|
|
399
|
+
})
|
|
400
|
+
proc.stdin.end(JSON.stringify(payload))
|
|
401
|
+
})
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export async function runAssert(rawArgs: string[]): Promise<void> {
|
|
405
|
+
if (rawArgs[0] === '--help' || rawArgs[0] === '-h' || rawArgs.length === 0) {
|
|
406
|
+
printHelp()
|
|
407
|
+
rnxExit(rawArgs.length === 0 ? 2 : 0)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const parsed = parseAssertArgs(rawArgs)
|
|
411
|
+
if ('error' in parsed) {
|
|
412
|
+
process.stderr.write(` assert: ${parsed.error}\n`)
|
|
413
|
+
rnxExit(2)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const { verbArgs, predicates, withinMs, negate, quiet, allowTimeout } = parsed
|
|
417
|
+
|
|
418
|
+
const verb = resolveReadVerb(verbArgs)
|
|
419
|
+
if (!verb) {
|
|
420
|
+
process.stderr.write(
|
|
421
|
+
` assert: unknown or non-read verb: ${verbArgs.join(' ')}\n` +
|
|
422
|
+
` supported: ${Array.from(READ_VERBS).sort().join(', ')}\n`,
|
|
423
|
+
)
|
|
424
|
+
rnxExit(2)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// default assertion when none given is --exists. matches the common
|
|
428
|
+
// "does this node / error / entry even show up?" check.
|
|
429
|
+
const effectivePredicates =
|
|
430
|
+
predicates.length > 0 ? predicates : [{ kind: '--exists', args: [] }]
|
|
431
|
+
|
|
432
|
+
const deadline = withinMs > 0 ? Date.now() + withinMs : 0
|
|
433
|
+
let attempts = 0
|
|
434
|
+
let lastFailReason = ''
|
|
435
|
+
let lastVerbSkipReason = ''
|
|
436
|
+
|
|
437
|
+
while (true) {
|
|
438
|
+
attempts++
|
|
439
|
+
const verbResult = await runVerbOnce(verbArgs)
|
|
440
|
+
if (!verbResult.ok) {
|
|
441
|
+
lastVerbSkipReason = verbResult.reason || 'verb failed'
|
|
442
|
+
if (Date.now() < deadline) {
|
|
443
|
+
await sleep(150)
|
|
444
|
+
continue
|
|
445
|
+
}
|
|
446
|
+
// out of retry budget. default is skip (125) because "verb couldn't
|
|
447
|
+
// evaluate" is distinct from "assertion evaluated and failed".
|
|
448
|
+
// --allow-timeout flips this to fail (1) — used when an auto-wait
|
|
449
|
+
// timeout inside the verb is a meaningful negative signal, not a
|
|
450
|
+
// "please bisect-skip this commit".
|
|
451
|
+
const exitCode = allowTimeout ? 1 : 125
|
|
452
|
+
if (!quiet) {
|
|
453
|
+
const label = exitCode === 1 ? 'fail' : 'skip'
|
|
454
|
+
process.stderr.write(` assert: ${label} — ${lastVerbSkipReason}\n`)
|
|
455
|
+
}
|
|
456
|
+
rnxExit(exitCode)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
let allPass = true
|
|
460
|
+
const reasons: string[] = []
|
|
461
|
+
try {
|
|
462
|
+
for (const pred of effectivePredicates) {
|
|
463
|
+
const r = await evalPredicate(pred, verbResult.payload)
|
|
464
|
+
reasons.push(`${pred.kind}: ${r.reason}`)
|
|
465
|
+
if (!r.ok) {
|
|
466
|
+
allPass = false
|
|
467
|
+
break
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
} catch (err: any) {
|
|
471
|
+
if (!quiet) process.stderr.write(` assert: skip — ${err.message}\n`)
|
|
472
|
+
rnxExit(125)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const pass = negate ? !allPass : allPass
|
|
476
|
+
if (pass) {
|
|
477
|
+
if (!quiet) {
|
|
478
|
+
process.stderr.write(
|
|
479
|
+
` assert: pass${attempts > 1 ? ` (attempt ${attempts})` : ''} — ${reasons.join('; ')}\n`,
|
|
480
|
+
)
|
|
481
|
+
}
|
|
482
|
+
rnxExit(0)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
lastFailReason = reasons.join('; ')
|
|
486
|
+
if (Date.now() < deadline) {
|
|
487
|
+
await sleep(150)
|
|
488
|
+
continue
|
|
489
|
+
}
|
|
490
|
+
if (!quiet) {
|
|
491
|
+
process.stderr.write(
|
|
492
|
+
` assert: fail${attempts > 1 ? ` after ${attempts} attempt(s)` : ''} — ${lastFailReason}\n`,
|
|
493
|
+
)
|
|
494
|
+
}
|
|
495
|
+
rnxExit(1)
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function sleep(ms: number): Promise<void> {
|
|
500
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function printHelp(): void {
|
|
504
|
+
process.stdout.write(`
|
|
505
|
+
rnx assert — run any read verb and convert its output into a bisect-friendly exit code.
|
|
506
|
+
|
|
507
|
+
usage:
|
|
508
|
+
rnx assert <verb> <verb-args...> <assertion-flags...>
|
|
509
|
+
|
|
510
|
+
exit codes:
|
|
511
|
+
0 assertion passed (git bisect: good)
|
|
512
|
+
1 assertion failed (git bisect: bad)
|
|
513
|
+
125 could not evaluate (git bisect: skip — app not loaded, verb crashed)
|
|
514
|
+
2 misuse (bad flags/verb)
|
|
515
|
+
|
|
516
|
+
assertion flags (AND'd when multiple are given):
|
|
517
|
+
--count <n> result array has exactly n items
|
|
518
|
+
--count-at-least <n> length >= n
|
|
519
|
+
--count-at-most <n> length <= n
|
|
520
|
+
--exists non-null, non-empty (default if no flag given)
|
|
521
|
+
--empty null or zero length
|
|
522
|
+
--contains <str> JSON.stringify(result) contains str (repeatable)
|
|
523
|
+
--not-contains <str> opposite
|
|
524
|
+
--matches <regex> result (stringified) matches regex
|
|
525
|
+
--not-matches <regex> opposite
|
|
526
|
+
--equals <literal> scalar equality
|
|
527
|
+
--has-path <dotted.path> nested path exists
|
|
528
|
+
--path-equals <path> <value> nested path equals value
|
|
529
|
+
--jq <expr> jq expression is truthy (requires jq on PATH)
|
|
530
|
+
--within <ms> retry verb + predicates until pass or deadline
|
|
531
|
+
--allow-timeout treat verb auto-wait timeout as a fail, not a skip
|
|
532
|
+
--negate, ! flip pass/fail (does not flip skip)
|
|
533
|
+
--quiet suppress the one-line pass/fail summary
|
|
534
|
+
|
|
535
|
+
examples:
|
|
536
|
+
rnx assert find --testid set-max-input --count 4
|
|
537
|
+
rnx assert describe --contains "swap-form-header"
|
|
538
|
+
rnx assert get errors --count 0
|
|
539
|
+
rnx assert find --testid submit --exists --within 3000
|
|
540
|
+
rnx assert describe --jq '.tree | contains("swap-form-header")'
|
|
541
|
+
`)
|
|
542
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
refreshSharedDesktopAuthSession,
|
|
3
|
+
sharedDesktopAuthAccounts,
|
|
4
|
+
} from '../../src/auth/shared-session'
|
|
5
|
+
import { CHOOSE_ACCOUNT_HINT, resolveCliAuth } from '../auth'
|
|
6
|
+
import { rnxExit } from '../run-rnx'
|
|
7
|
+
|
|
8
|
+
function printHelp(): void {
|
|
9
|
+
console.log(`
|
|
10
|
+
rnx auth — inspect CLI auth
|
|
11
|
+
|
|
12
|
+
usage:
|
|
13
|
+
rnx auth
|
|
14
|
+
|
|
15
|
+
API keys are read from RNX_API_KEY.
|
|
16
|
+
Use \`rnx login\` to sign in and \`rnx logout\` to clear the desktop session.
|
|
17
|
+
`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function printStatus(): Promise<number> {
|
|
21
|
+
const auth = resolveCliAuth()
|
|
22
|
+
if (!auth) {
|
|
23
|
+
console.log(' not signed in')
|
|
24
|
+
console.log(' set RNX_API_KEY=sk_rnx_... or run `rnx login`')
|
|
25
|
+
return 1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (auth.kind === 'api-key') {
|
|
29
|
+
console.log(` api key ${auth.secret.slice(0, 14)}...`)
|
|
30
|
+
console.log(` source: ${auth.envName} env var`)
|
|
31
|
+
return 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (auth.kind === 'github') {
|
|
35
|
+
console.log(` github token (${auth.source})`)
|
|
36
|
+
console.log(` repo: ${auth.repoId}`)
|
|
37
|
+
return 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const current = await refreshSharedDesktopAuthSession(auth.origin)
|
|
41
|
+
const user = current?.user
|
|
42
|
+
const accounts = sharedDesktopAuthAccounts(user)
|
|
43
|
+
const accountId = current?.accountId ?? auth.accountId
|
|
44
|
+
const account = accounts.find((candidate) => candidate.id === accountId)
|
|
45
|
+
console.log(` ${user?.email || user?.name || user?.id || 'signed in'}`)
|
|
46
|
+
console.log(
|
|
47
|
+
` account: ${
|
|
48
|
+
account ? `${account.name} (${account.id})` : (accountId ?? CHOOSE_ACCOUNT_HINT)
|
|
49
|
+
}`,
|
|
50
|
+
)
|
|
51
|
+
console.log(` origin: ${current?.origin ?? auth.origin}`)
|
|
52
|
+
if (current?.updatedAt) console.log(` updated: ${current.updatedAt}`)
|
|
53
|
+
return 0
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function runAuth(args: string[]): Promise<void> {
|
|
57
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
58
|
+
printHelp()
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const sub = args.find((arg) => !arg.startsWith('-'))
|
|
63
|
+
if (!sub || sub === 'status') {
|
|
64
|
+
process.exitCode = await printStatus()
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.error(` unknown auth command: ${sub}`)
|
|
69
|
+
printHelp()
|
|
70
|
+
rnxExit(1)
|
|
71
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// ProjectFilePlane over an already-built bundle held in memory.
|
|
2
|
+
//
|
|
3
|
+
// a prebuilt Box has no sources, so there is no checkout to index and nothing
|
|
4
|
+
// on disk to stay in step with: the whole project is the bundle the page boots.
|
|
5
|
+
// CheckoutFilePlane cannot serve this because it asks git what belongs to the
|
|
6
|
+
// project, and a bundle produced by a build lives outside any checkout.
|
|
7
|
+
//
|
|
8
|
+
// the bundle is split here rather than by the caller, so the one place that
|
|
9
|
+
// decides where the parts go is the one place that decides how big they are.
|
|
10
|
+
|
|
11
|
+
import { PREBUILT_BUNDLE_PART_BYTES, prebuiltBundlePartPath } from '@rnx/box/template'
|
|
12
|
+
import type { ProjectFileMeta, ProjectFilePlane } from '@rnx/box/plane'
|
|
13
|
+
|
|
14
|
+
export class BundleFilePlane implements ProjectFilePlane {
|
|
15
|
+
private readonly parts = new Map<string, Uint8Array>()
|
|
16
|
+
private readonly entries = new Map<string, ProjectFileMeta>()
|
|
17
|
+
|
|
18
|
+
constructor(bundle: Uint8Array) {
|
|
19
|
+
if (bundle.byteLength === 0) throw new Error('a Box bundle cannot be empty')
|
|
20
|
+
for (
|
|
21
|
+
let offset = 0, index = 0;
|
|
22
|
+
offset < bundle.byteLength;
|
|
23
|
+
offset += PREBUILT_BUNDLE_PART_BYTES, index++
|
|
24
|
+
) {
|
|
25
|
+
const part = bundle.subarray(offset, offset + PREBUILT_BUNDLE_PART_BYTES)
|
|
26
|
+
this.record(prebuiltBundlePartPath(index), part)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private record(path: string, content: Uint8Array): void {
|
|
31
|
+
this.parts.set(path, content)
|
|
32
|
+
this.entries.set(path, {
|
|
33
|
+
size: content.byteLength,
|
|
34
|
+
readOnly: false,
|
|
35
|
+
// a Metro bundle is JavaScript. the flag is what decides whether a shell
|
|
36
|
+
// may overwrite a file with text, and marking it binary would make the
|
|
37
|
+
// bundle unwritable from inside the box for no reason.
|
|
38
|
+
binary: false,
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
index(): ReadonlyMap<string, ProjectFileMeta> {
|
|
43
|
+
return this.entries
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// nothing else can change this plane, so the index is already current.
|
|
47
|
+
refresh(): Promise<void> {
|
|
48
|
+
return Promise.resolve()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async read(path: string): Promise<string> {
|
|
52
|
+
return new TextDecoder().decode(await this.readBytes(path))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
readBytes(path: string): Promise<Uint8Array> {
|
|
56
|
+
const part = this.parts.get(path)
|
|
57
|
+
if (!part) return Promise.reject(new Error(`no such file: ${path}`))
|
|
58
|
+
return Promise.resolve(part)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
write(path: string, content: Uint8Array): Promise<void> {
|
|
62
|
+
this.record(path, content)
|
|
63
|
+
return Promise.resolve()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
delete(path: string): Promise<void> {
|
|
67
|
+
this.parts.delete(path)
|
|
68
|
+
this.entries.delete(path)
|
|
69
|
+
return Promise.resolve()
|
|
70
|
+
}
|
|
71
|
+
}
|