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,98 @@
|
|
|
1
|
+
// running an rnx command inside the calling process instead of spawning the CLI.
|
|
2
|
+
//
|
|
3
|
+
// the parse and the dispatch here are the CLI's own: parseRnxArgs, the command
|
|
4
|
+
// registry behind it, and the same per-command handlers main.ts reaches. what
|
|
5
|
+
// changes is the transport and the boundary. the caller hands in the WsBridge,
|
|
6
|
+
// so one vocabulary and one parser run against three of them: the local ws
|
|
7
|
+
// daemon, a cloud box's commands route, and, inside a box shell in workerd, the
|
|
8
|
+
// Box Durable Object's own command router with no network hop.
|
|
9
|
+
//
|
|
10
|
+
// the boundary is run-rnx.ts: output comes back as a value rather than reaching
|
|
11
|
+
// a terminal, and a command that would have called process.exit throws instead,
|
|
12
|
+
// which is what makes this safe to call in workerd, where process.exit cancels
|
|
13
|
+
// the whole request.
|
|
14
|
+
|
|
15
|
+
import { parseRnxArgs, TOP_LEVEL_RUNTIME_COMMANDS } from './parse-args'
|
|
16
|
+
import { type RnxRunResult, runRnx } from './run-rnx'
|
|
17
|
+
import type { ParsedBridgeCliArgs, WsBridge } from './ws-bridge'
|
|
18
|
+
|
|
19
|
+
// the verbs that reach a simulator through the bridge and nothing else. every
|
|
20
|
+
// other registered command needs the machine rnx is installed on: a device to
|
|
21
|
+
// boot, a browser to open, a credential store, the bridge daemon, or the local
|
|
22
|
+
// filesystem. `state` and `reset` sit outside runInspect but take the same
|
|
23
|
+
// bridge, so they belong here too.
|
|
24
|
+
const IN_PROCESS_COMMANDS: ReadonlySet<string> = new Set([
|
|
25
|
+
...TOP_LEVEL_RUNTIME_COMMANDS,
|
|
26
|
+
'state',
|
|
27
|
+
'reset',
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
const RUNNABLE = [...IN_PROCESS_COMMANDS].sort().join(', ')
|
|
31
|
+
|
|
32
|
+
export interface RnxInProcessOptions {
|
|
33
|
+
/** the command and its arguments, with no node/script prefix. */
|
|
34
|
+
argv: string[]
|
|
35
|
+
/**
|
|
36
|
+
* the transport the command runs against. it takes the parsed bridge args
|
|
37
|
+
* because the local host builds its bridge out of `--sim`, `--port`, and
|
|
38
|
+
* `--timeout`, while a box ignores all three and answers on its own socket.
|
|
39
|
+
*/
|
|
40
|
+
createBridge: (parsed: ParsedBridgeCliArgs) => WsBridge
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function runRnxInProcess(
|
|
44
|
+
options: RnxInProcessOptions,
|
|
45
|
+
): Promise<RnxRunResult> {
|
|
46
|
+
return runRnx(options.argv, async (argv) => {
|
|
47
|
+
const parsed = parseRnxArgs(argv)
|
|
48
|
+
for (const warning of parsed.warnings) console.error(warning)
|
|
49
|
+
const command = parsed.command
|
|
50
|
+
if (!command || !IN_PROCESS_COMMANDS.has(command)) {
|
|
51
|
+
const named = command ? `rnx ${command}` : 'rnx'
|
|
52
|
+
console.error(
|
|
53
|
+
` ${named} is not available here; this rnx drives a simulator and has no machine to run on. runs here: ${RUNNABLE}`,
|
|
54
|
+
)
|
|
55
|
+
return 1
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const simTarget = parsed.globalFlags['sim'] ?? parsed.globalFlags['session']
|
|
59
|
+
const commandArgs =
|
|
60
|
+
typeof simTarget === 'string'
|
|
61
|
+
? ['--sim', simTarget, ...parsed.commandArgs]
|
|
62
|
+
: parsed.commandArgs
|
|
63
|
+
if (commandArgs.includes('--then')) {
|
|
64
|
+
console.error(
|
|
65
|
+
' rnx do --then is not available here; a chain runs against a sim connection rather than the bridge. run the actions one at a time.',
|
|
66
|
+
)
|
|
67
|
+
return 1
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const port = parsed.globalFlags['port']
|
|
71
|
+
const opts = {
|
|
72
|
+
...(typeof port === 'number' ? { port } : {}),
|
|
73
|
+
createBridge: options.createBridge,
|
|
74
|
+
}
|
|
75
|
+
if (command === 'state') {
|
|
76
|
+
const { runState } = await import('./commands/state')
|
|
77
|
+
return runState(commandArgs, opts)
|
|
78
|
+
}
|
|
79
|
+
if (command === 'reset') {
|
|
80
|
+
const { runReset } = await import('./commands/reset')
|
|
81
|
+
return runReset(commandArgs, opts)
|
|
82
|
+
}
|
|
83
|
+
const { runInspect } = await import('./commands/inspect')
|
|
84
|
+
await runInspect([command, ...commandArgs], { ...opts, verbose: parsed.verbose })
|
|
85
|
+
return 0
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// the transport contract and the command's own name, re-exported so a host
|
|
90
|
+
// that only wants to register this entry and supply a bridge (a Box Durable
|
|
91
|
+
// Object, say) has one module to import.
|
|
92
|
+
export { rnxPublicBrand } from '../src/public-brand'
|
|
93
|
+
export type {
|
|
94
|
+
BridgeClaimResult,
|
|
95
|
+
BridgeSimInfo,
|
|
96
|
+
ParsedBridgeCliArgs,
|
|
97
|
+
WsBridge,
|
|
98
|
+
} from './ws-bridge'
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// shared test-run registration for both runners (Maestro / Detox). posts a
|
|
2
|
+
// `sootRun` row to /api/sootsim/test-runs so the org
|
|
3
|
+
// dashboard and PR comments get a pass/fail list with git provenance and a
|
|
4
|
+
// link into the hosted replay (the uploaded preview share).
|
|
5
|
+
//
|
|
6
|
+
// registration policy: a run registers when it produced a hosted share
|
|
7
|
+
// (--preview) or when it runs in CI (github-token / actions env) — local
|
|
8
|
+
// iteration without --preview stays local. registration failures warn and
|
|
9
|
+
// never change the run's exit code; the test result is the playback result.
|
|
10
|
+
|
|
11
|
+
import { execSync } from 'child_process'
|
|
12
|
+
import { authHeaderValue, resolveCliAuth, type CliAuth } from './auth'
|
|
13
|
+
import type { SootSimFlowTraceStep } from './bridge-flow-runner'
|
|
14
|
+
|
|
15
|
+
export type RunKind = 'maestro' | 'detox'
|
|
16
|
+
|
|
17
|
+
export type RunProvenance = {
|
|
18
|
+
owner: string | null
|
|
19
|
+
repo: string | null
|
|
20
|
+
branch: string | null
|
|
21
|
+
commitSha: string | null
|
|
22
|
+
githubUsername: string | null
|
|
23
|
+
pullRequestNumber: number | null
|
|
24
|
+
pullRequestTitle: string | null
|
|
25
|
+
githubRunId: string | null
|
|
26
|
+
source: 'cli' | 'ci'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function gitOutput(command: string): string | null {
|
|
30
|
+
try {
|
|
31
|
+
const out = execSync(command, {
|
|
32
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
33
|
+
timeout: 3000,
|
|
34
|
+
encoding: 'utf8',
|
|
35
|
+
}).trim()
|
|
36
|
+
return out || null
|
|
37
|
+
} catch {
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parsePositiveInt(value: string | undefined): number | null {
|
|
43
|
+
const trimmed = value?.trim()
|
|
44
|
+
if (!trimmed || !/^\d+$/.test(trimmed)) return null
|
|
45
|
+
return Number(trimmed)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// owner/repo from the checkout's github remote, for local runs with no
|
|
49
|
+
// CONTRAST_REPO/GITHUB_REPOSITORY env. runs are repo-scoped on the dashboard
|
|
50
|
+
// (/org/<owner>/<repo>/runs), so a repo-less registration has no home — the
|
|
51
|
+
// server 400s it. handles ssh (git@github.com:o/r.git) and https
|
|
52
|
+
// (https://github.com/o/r(.git)) remote shapes.
|
|
53
|
+
function ownerRepoFromGitRemote(): { owner: string; repo: string } | null {
|
|
54
|
+
const url = gitOutput('git remote get-url origin')
|
|
55
|
+
if (!url) return null
|
|
56
|
+
const match = url.match(/github\.com[/:]([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i)
|
|
57
|
+
if (!match) return null
|
|
58
|
+
return { owner: match[1], repo: match[2] }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// git provenance for the current run. in GitHub Actions the CONTRAST_*/GITHUB_*
|
|
62
|
+
// env is authoritative (the checkout can be a detached merge ref); locally
|
|
63
|
+
// we ask git itself.
|
|
64
|
+
export function resolveRunProvenance(): RunProvenance {
|
|
65
|
+
const isActions = process.env.GITHUB_ACTIONS === 'true'
|
|
66
|
+
const contrastBranch = process.env.CONTRAST_BRANCH?.trim() || null
|
|
67
|
+
const contrastSha = process.env.CONTRAST_SHA?.trim() || null
|
|
68
|
+
const pullRequestTitle = process.env.CONTRAST_PR_TITLE?.trim() || null
|
|
69
|
+
const repoSlug = (
|
|
70
|
+
process.env.CONTRAST_REPO ||
|
|
71
|
+
process.env.GITHUB_REPOSITORY ||
|
|
72
|
+
''
|
|
73
|
+
).trim()
|
|
74
|
+
const [envOwner, envRepo] = repoSlug.includes('/')
|
|
75
|
+
? repoSlug.split('/', 2)
|
|
76
|
+
: [null, null]
|
|
77
|
+
|
|
78
|
+
if (isActions) {
|
|
79
|
+
// pull_request events: GITHUB_HEAD_REF is the PR head branch and
|
|
80
|
+
// GITHUB_REF looks like refs/pull/<n>/merge. push events: GITHUB_REF_NAME.
|
|
81
|
+
const prFromRef = process.env.GITHUB_REF?.match(/^refs\/pull\/(\d+)\//)?.[1]
|
|
82
|
+
return {
|
|
83
|
+
owner: envOwner,
|
|
84
|
+
repo: envRepo,
|
|
85
|
+
branch:
|
|
86
|
+
contrastBranch ||
|
|
87
|
+
process.env.GITHUB_HEAD_REF?.trim() ||
|
|
88
|
+
process.env.GITHUB_REF_NAME?.trim() ||
|
|
89
|
+
null,
|
|
90
|
+
commitSha: contrastSha || process.env.GITHUB_SHA?.trim() || null,
|
|
91
|
+
githubUsername: process.env.GITHUB_ACTOR?.trim() || null,
|
|
92
|
+
pullRequestNumber:
|
|
93
|
+
parsePositiveInt(process.env.CONTRAST_PR_NUMBER) ?? parsePositiveInt(prFromRef),
|
|
94
|
+
pullRequestTitle,
|
|
95
|
+
githubRunId: process.env.GITHUB_RUN_ID?.trim() || null,
|
|
96
|
+
source: 'ci',
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const branch = contrastBranch ?? gitOutput('git rev-parse --abbrev-ref HEAD')
|
|
101
|
+
// env slug wins when present; otherwise infer from the github remote so a
|
|
102
|
+
// a plain local `rnx maestro test --preview` still lands on its repo's dashboard.
|
|
103
|
+
const remote = envOwner ? null : ownerRepoFromGitRemote()
|
|
104
|
+
return {
|
|
105
|
+
owner: envOwner ?? remote?.owner ?? null,
|
|
106
|
+
repo: envRepo ?? remote?.repo ?? null,
|
|
107
|
+
branch: branch === 'HEAD' ? null : branch,
|
|
108
|
+
commitSha: contrastSha ?? gitOutput('git rev-parse HEAD'),
|
|
109
|
+
githubUsername: null,
|
|
110
|
+
pullRequestNumber: parsePositiveInt(process.env.CONTRAST_PR_NUMBER),
|
|
111
|
+
pullRequestTitle,
|
|
112
|
+
githubRunId: null,
|
|
113
|
+
source: 'cli',
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function stepSummaryFromTrace(steps: SootSimFlowTraceStep[]): {
|
|
118
|
+
stepCount: number | null
|
|
119
|
+
failedStepIndex: number | null
|
|
120
|
+
} {
|
|
121
|
+
if (steps.length === 0) return { stepCount: null, failedStepIndex: null }
|
|
122
|
+
const failed = steps.find((step) => step.status === 'failure')
|
|
123
|
+
return {
|
|
124
|
+
stepCount: steps.length,
|
|
125
|
+
failedStepIndex: failed ? failed.stepIndex : null,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// whether this run should register at all — see the policy note at the top.
|
|
130
|
+
export function shouldRegisterRun(args: {
|
|
131
|
+
uploadedShare: boolean
|
|
132
|
+
auth: CliAuth | null
|
|
133
|
+
}): boolean {
|
|
134
|
+
if (!args.auth) return false
|
|
135
|
+
if (args.uploadedShare) return true
|
|
136
|
+
return args.auth.kind === 'github' || process.env.GITHUB_ACTIONS === 'true'
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export type RegisterRunArgs = {
|
|
140
|
+
origin: string
|
|
141
|
+
kind: RunKind
|
|
142
|
+
status: 'passed' | 'failed'
|
|
143
|
+
name?: string | null
|
|
144
|
+
prompt?: string | null
|
|
145
|
+
summary?: string | null
|
|
146
|
+
failureMessage?: string | null
|
|
147
|
+
previewShareId?: string | null
|
|
148
|
+
// explicit --owner/--repo flags override env/git detection
|
|
149
|
+
owner?: string | null
|
|
150
|
+
repo?: string | null
|
|
151
|
+
durationMs?: number | null
|
|
152
|
+
flowYamlSizeBytes?: number | null
|
|
153
|
+
stepCount?: number | null
|
|
154
|
+
failedStepIndex?: number | null
|
|
155
|
+
auth?: CliAuth | null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type RegisteredRun = {
|
|
159
|
+
id: string
|
|
160
|
+
previewUrl: string | null
|
|
161
|
+
traceUrl: string | null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export async function registerRun(args: RegisterRunArgs): Promise<RegisteredRun | null> {
|
|
165
|
+
const auth = args.auth ?? resolveCliAuth()
|
|
166
|
+
if (!auth) return null
|
|
167
|
+
const provenance = resolveRunProvenance()
|
|
168
|
+
const owner =
|
|
169
|
+
args.owner ?? (auth.kind === 'github' ? auth.owner : null) ?? provenance.owner
|
|
170
|
+
const repo = args.repo ?? (auth.kind === 'github' ? auth.repo : null) ?? provenance.repo
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const res = await fetch(`${args.origin.replace(/\/$/, '')}/api/sootsim/test-runs`, {
|
|
174
|
+
method: 'POST',
|
|
175
|
+
headers: {
|
|
176
|
+
'content-type': 'application/json',
|
|
177
|
+
authorization: authHeaderValue(auth),
|
|
178
|
+
},
|
|
179
|
+
body: JSON.stringify({
|
|
180
|
+
kind: args.kind,
|
|
181
|
+
source: provenance.source,
|
|
182
|
+
name: args.name ?? undefined,
|
|
183
|
+
prompt: args.prompt ?? undefined,
|
|
184
|
+
summary: args.summary ?? undefined,
|
|
185
|
+
status: args.status,
|
|
186
|
+
failureMessage: args.failureMessage ?? undefined,
|
|
187
|
+
previewShareId: args.previewShareId ?? undefined,
|
|
188
|
+
owner: owner ?? undefined,
|
|
189
|
+
repo: repo ?? undefined,
|
|
190
|
+
branch: provenance.branch ?? undefined,
|
|
191
|
+
commitSha: provenance.commitSha ?? undefined,
|
|
192
|
+
githubUsername: provenance.githubUsername ?? undefined,
|
|
193
|
+
pullRequestNumber: provenance.pullRequestNumber ?? undefined,
|
|
194
|
+
githubRunId: provenance.githubRunId ?? undefined,
|
|
195
|
+
stepCount: args.stepCount ?? undefined,
|
|
196
|
+
failedStepIndex: args.failedStepIndex ?? undefined,
|
|
197
|
+
durationMs: args.durationMs ?? undefined,
|
|
198
|
+
flowYamlSizeBytes: args.flowYamlSizeBytes ?? undefined,
|
|
199
|
+
}),
|
|
200
|
+
})
|
|
201
|
+
if (!res.ok) {
|
|
202
|
+
const text = await res.text().catch(() => '')
|
|
203
|
+
console.warn(
|
|
204
|
+
` warn: run registration failed: /api/sootsim/test-runs ${res.status}${text ? `: ${text.slice(0, 200)}` : ''}`,
|
|
205
|
+
)
|
|
206
|
+
return null
|
|
207
|
+
}
|
|
208
|
+
const body = (await res.json()) as {
|
|
209
|
+
run?: { id?: string; traceUrl?: string | null; previewUrl?: string | null }
|
|
210
|
+
}
|
|
211
|
+
if (!body.run?.id) {
|
|
212
|
+
console.warn(' warn: run registration returned no id')
|
|
213
|
+
return null
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
id: body.run.id,
|
|
217
|
+
previewUrl: body.run.previewUrl ?? null,
|
|
218
|
+
traceUrl: body.run.traceUrl ?? null,
|
|
219
|
+
}
|
|
220
|
+
} catch (err) {
|
|
221
|
+
console.warn(
|
|
222
|
+
` warn: run registration failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
223
|
+
)
|
|
224
|
+
return null
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRnxCommandInvocation,
|
|
3
|
+
getRnxCommandAvailability,
|
|
4
|
+
getRnxFirstPositional,
|
|
5
|
+
} from './command-registry'
|
|
6
|
+
import { renderRnxHelp } from './help-core'
|
|
7
|
+
import { resolveHiddenRuntimeAlias } from './hidden-runtime-alias'
|
|
8
|
+
import { parseRnxArgs } from './parse-args'
|
|
9
|
+
import type {
|
|
10
|
+
CommandArtifact,
|
|
11
|
+
CommandExecutionResult,
|
|
12
|
+
CommandWriter,
|
|
13
|
+
RunRnxCoreOptions,
|
|
14
|
+
SemanticInvocation,
|
|
15
|
+
} from './command-contract'
|
|
16
|
+
|
|
17
|
+
interface CapturedCommandWriter {
|
|
18
|
+
writer: CommandWriter
|
|
19
|
+
stdout: string[]
|
|
20
|
+
stderr: string[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function createCapturedWriter(writer?: CommandWriter): CapturedCommandWriter {
|
|
24
|
+
const stdout: string[] = []
|
|
25
|
+
const stderr: string[] = []
|
|
26
|
+
return {
|
|
27
|
+
stdout,
|
|
28
|
+
stderr,
|
|
29
|
+
writer: {
|
|
30
|
+
stdout(text) {
|
|
31
|
+
stdout.push(text)
|
|
32
|
+
writer?.stdout(text)
|
|
33
|
+
},
|
|
34
|
+
stderr(text) {
|
|
35
|
+
stderr.push(text)
|
|
36
|
+
writer?.stderr(text)
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function result(
|
|
43
|
+
captured: CapturedCommandWriter,
|
|
44
|
+
exitCode: number,
|
|
45
|
+
invocations: SemanticInvocation[] = [],
|
|
46
|
+
artifacts: CommandArtifact[] = [],
|
|
47
|
+
): CommandExecutionResult {
|
|
48
|
+
return {
|
|
49
|
+
exitCode,
|
|
50
|
+
stdout: captured.stdout.join(''),
|
|
51
|
+
stderr: captured.stderr.join(''),
|
|
52
|
+
invocations,
|
|
53
|
+
artifacts,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function runRnxCore(
|
|
58
|
+
argv: string[],
|
|
59
|
+
options: RunRnxCoreOptions,
|
|
60
|
+
): Promise<CommandExecutionResult> {
|
|
61
|
+
const captured = createCapturedWriter(options.writer)
|
|
62
|
+
const signal = options.signal ?? new AbortController().signal
|
|
63
|
+
let parsed = parseRnxArgs(argv)
|
|
64
|
+
for (const warning of parsed.warnings) captured.writer.stderr(`${warning}\n`)
|
|
65
|
+
|
|
66
|
+
if (!parsed.command && parsed.commandArgs.length > 0) {
|
|
67
|
+
parsed = resolveHiddenRuntimeAlias(parsed)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (parsed.version) {
|
|
71
|
+
captured.writer.stderr(
|
|
72
|
+
'rnx --version is unavailable in the browser backend; use the node backend.\n',
|
|
73
|
+
)
|
|
74
|
+
return result(captured, 1)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!parsed.command) {
|
|
78
|
+
if (parsed.commandArgs.length > 0) {
|
|
79
|
+
const attempted = getRnxFirstPositional(parsed.commandArgs) ?? parsed.commandArgs[0]
|
|
80
|
+
captured.writer.stderr(`unknown command: ${attempted}\n`)
|
|
81
|
+
return result(captured, 1)
|
|
82
|
+
}
|
|
83
|
+
captured.writer.stdout(`${renderRnxHelp({ backend: options.backend.kind })}\n`)
|
|
84
|
+
return result(captured, 0)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const command = parsed.command
|
|
88
|
+
const helpRequested =
|
|
89
|
+
parsed.help ||
|
|
90
|
+
parsed.commandArgs.includes('--help') ||
|
|
91
|
+
parsed.commandArgs.includes('-h')
|
|
92
|
+
if (helpRequested) {
|
|
93
|
+
const grouping = new Set(['do', 'get', 'debug', 'wait'])
|
|
94
|
+
const subVerb = getRnxFirstPositional(parsed.commandArgs)
|
|
95
|
+
captured.writer.stdout(
|
|
96
|
+
`${renderRnxHelp({
|
|
97
|
+
backend: options.backend.kind,
|
|
98
|
+
command,
|
|
99
|
+
commandArgs: parsed.commandArgs,
|
|
100
|
+
prefer: grouping.has(command) && subVerb ? 'verb' : undefined,
|
|
101
|
+
group: grouping.has(command) ? command : undefined,
|
|
102
|
+
})}\n`,
|
|
103
|
+
)
|
|
104
|
+
return result(captured, 0)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const availability = getRnxCommandAvailability(
|
|
108
|
+
command,
|
|
109
|
+
parsed.commandArgs,
|
|
110
|
+
options.backend.kind,
|
|
111
|
+
)
|
|
112
|
+
if (!availability.available) {
|
|
113
|
+
captured.writer.stderr(`${availability.reason ?? `rnx ${command} is unavailable`}\n`)
|
|
114
|
+
return result(captured, 1)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (signal.aborted) {
|
|
118
|
+
captured.writer.stderr('rnx: command cancelled\n')
|
|
119
|
+
return result(captured, 1)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const invocation = createRnxCommandInvocation({
|
|
123
|
+
command,
|
|
124
|
+
args: parsed.commandArgs,
|
|
125
|
+
globalFlags: parsed.globalFlags,
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const execution = await options.backend.execute(invocation, {
|
|
130
|
+
writer: captured.writer,
|
|
131
|
+
signal,
|
|
132
|
+
})
|
|
133
|
+
return result(
|
|
134
|
+
captured,
|
|
135
|
+
execution.exitCode,
|
|
136
|
+
execution.invocations,
|
|
137
|
+
execution.artifacts,
|
|
138
|
+
)
|
|
139
|
+
} catch (error) {
|
|
140
|
+
const message = signal.aborted
|
|
141
|
+
? 'command cancelled'
|
|
142
|
+
: error instanceof Error
|
|
143
|
+
? error.message
|
|
144
|
+
: String(error)
|
|
145
|
+
captured.writer.stderr(`rnx: ${message}\n`)
|
|
146
|
+
return result(captured, 1)
|
|
147
|
+
}
|
|
148
|
+
}
|
package/cli/run-rnx.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// node/workerd compatibility for command modules that still write through
|
|
2
|
+
// process and console. browser-safe commands use run-rnx-core.ts, whose
|
|
3
|
+
// explicit context carries its writer, cancellation signal, semantic
|
|
4
|
+
// invocations, and artifacts without touching ambient process state.
|
|
5
|
+
//
|
|
6
|
+
// two things stand between the command modules and an in-process call, and
|
|
7
|
+
// this module owns both:
|
|
8
|
+
//
|
|
9
|
+
// output — the modules write to console.* and process.std*.write, ~1,777
|
|
10
|
+
// sites. they are not rewritten to take a writer. instead the sinks are
|
|
11
|
+
// resolved from async context, so two commands running concurrently in one
|
|
12
|
+
// isolate keep their output separate. RAN: a global current-sink instead of
|
|
13
|
+
// AsyncLocalStorage interleaves them, which is what makes the context
|
|
14
|
+
// version necessary rather than merely tidy.
|
|
15
|
+
//
|
|
16
|
+
// exit — a command that calls process.exit ends the host process instead of
|
|
17
|
+
// returning a code to its caller. `rnxExit` throws instead, and `runRnx`
|
|
18
|
+
// turns it back into a code at the boundary.
|
|
19
|
+
//
|
|
20
|
+
// AsyncLocalStorage keeps this compatibility module node/workerd only. migrate
|
|
21
|
+
// a command to the explicit core before exposing it through a browser backend.
|
|
22
|
+
|
|
23
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
24
|
+
|
|
25
|
+
export interface RnxRunResult {
|
|
26
|
+
stdout: string
|
|
27
|
+
stderr: string
|
|
28
|
+
exitCode: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface RnxSinks {
|
|
32
|
+
stdout: string[]
|
|
33
|
+
stderr: string[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// thrown by `rnxExit` in place of process.exit. every catch that reports an
|
|
37
|
+
// error must re-throw this before reporting, or it turns a deliberate exit
|
|
38
|
+
// into a spurious "command failed" line with the sentinel's own text.
|
|
39
|
+
export class RnxExit extends Error {
|
|
40
|
+
readonly code: number
|
|
41
|
+
|
|
42
|
+
constructor(code: number) {
|
|
43
|
+
super(`rnx exited with code ${code}`)
|
|
44
|
+
this.name = 'RnxExit'
|
|
45
|
+
this.code = code
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const sinks = new AsyncLocalStorage<RnxSinks>()
|
|
50
|
+
|
|
51
|
+
// stop a command's deliberate exit from being reported as a failure. the
|
|
52
|
+
// guard every reporting catch needs, in one place so the call sites stay one
|
|
53
|
+
// line.
|
|
54
|
+
export function rethrowIfExit(error: unknown): void {
|
|
55
|
+
if (error instanceof RnxExit) throw error
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// the in-process replacement for process.exit. returns `never` exactly as
|
|
59
|
+
// process.exit does, so it substitutes without changing any call site's types.
|
|
60
|
+
export function rnxExit(code: number): never {
|
|
61
|
+
throw new RnxExit(code)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function write(stream: 'stdout' | 'stderr', text: string): boolean {
|
|
65
|
+
const current = sinks.getStore()
|
|
66
|
+
if (!current) return false
|
|
67
|
+
current[stream].push(text)
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let installed = false
|
|
72
|
+
|
|
73
|
+
// replace the process-wide writers once. each resolves its destination from
|
|
74
|
+
// async context, so a call made outside any `runRnx` still reaches the real
|
|
75
|
+
// terminal and the host CLI keeps behaving exactly as it did.
|
|
76
|
+
function installCapture(): void {
|
|
77
|
+
if (installed) return
|
|
78
|
+
installed = true
|
|
79
|
+
|
|
80
|
+
const realLog = console.log.bind(console)
|
|
81
|
+
const realError = console.error.bind(console)
|
|
82
|
+
const realWarn = console.warn.bind(console)
|
|
83
|
+
const realStdout = process.stdout.write.bind(process.stdout)
|
|
84
|
+
const realStderr = process.stderr.write.bind(process.stderr)
|
|
85
|
+
|
|
86
|
+
const line = (args: unknown[]): string => `${args.map(String).join(' ')}\n`
|
|
87
|
+
|
|
88
|
+
console.log = (...args: unknown[]) => {
|
|
89
|
+
if (!write('stdout', line(args))) realLog(...args)
|
|
90
|
+
}
|
|
91
|
+
console.error = (...args: unknown[]) => {
|
|
92
|
+
if (!write('stderr', line(args))) realError(...args)
|
|
93
|
+
}
|
|
94
|
+
console.warn = (...args: unknown[]) => {
|
|
95
|
+
if (!write('stderr', line(args))) realWarn(...args)
|
|
96
|
+
}
|
|
97
|
+
process.stdout.write = (chunk: string | Uint8Array, ...rest: unknown[]) => {
|
|
98
|
+
if (
|
|
99
|
+
write('stdout', typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk))
|
|
100
|
+
) {
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
return Reflect.apply(realStdout, process.stdout, [chunk, ...rest])
|
|
104
|
+
}
|
|
105
|
+
process.stderr.write = (chunk: string | Uint8Array, ...rest: unknown[]) => {
|
|
106
|
+
if (
|
|
107
|
+
write('stderr', typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk))
|
|
108
|
+
) {
|
|
109
|
+
return true
|
|
110
|
+
}
|
|
111
|
+
return Reflect.apply(realStderr, process.stderr, [chunk, ...rest])
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// run one rnx invocation and return what it wrote plus its exit code, instead
|
|
116
|
+
// of writing to the terminal and ending the process. `argv` is the command and
|
|
117
|
+
// its arguments with no node/script prefix, e.g. ['describe', '--json'].
|
|
118
|
+
export async function runRnx(
|
|
119
|
+
argv: string[],
|
|
120
|
+
dispatch: (argv: string[]) => Promise<number>,
|
|
121
|
+
): Promise<RnxRunResult> {
|
|
122
|
+
installCapture()
|
|
123
|
+
const current: RnxSinks = { stdout: [], stderr: [] }
|
|
124
|
+
const exitCode = await sinks.run(current, async () => {
|
|
125
|
+
try {
|
|
126
|
+
return await dispatch(argv)
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error instanceof RnxExit) return error.code
|
|
129
|
+
throw error
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
return {
|
|
133
|
+
stdout: current.stdout.join(''),
|
|
134
|
+
stderr: current.stderr.join(''),
|
|
135
|
+
exitCode,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { RNX_CHANGELOG_API_URL, RNX_CHANGELOG_URL } from '../src/runtime-delivery'
|
|
2
|
+
import type { ChangelogEntry } from '../../../scripts/changelog/types'
|
|
3
|
+
|
|
4
|
+
function isChangelogEntry(value: unknown): value is ChangelogEntry {
|
|
5
|
+
if (!value || typeof value !== 'object') return false
|
|
6
|
+
const sections = Reflect.get(value, 'sections')
|
|
7
|
+
return (
|
|
8
|
+
typeof Reflect.get(value, 'title') === 'string' &&
|
|
9
|
+
Array.isArray(sections) &&
|
|
10
|
+
sections.every(
|
|
11
|
+
(section) =>
|
|
12
|
+
section &&
|
|
13
|
+
typeof section === 'object' &&
|
|
14
|
+
typeof Reflect.get(section, 'title') === 'string' &&
|
|
15
|
+
Array.isArray(Reflect.get(section, 'items')) &&
|
|
16
|
+
Reflect.get(section, 'items').every(
|
|
17
|
+
(item: unknown) =>
|
|
18
|
+
item &&
|
|
19
|
+
typeof item === 'object' &&
|
|
20
|
+
typeof Reflect.get(item, 'summary') === 'string' &&
|
|
21
|
+
typeof Reflect.get(item, 'hash') === 'string',
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function fetchRuntimeReleaseNotes(
|
|
28
|
+
version: string,
|
|
29
|
+
fetchImpl: typeof fetch = fetch,
|
|
30
|
+
): Promise<ChangelogEntry | null> {
|
|
31
|
+
try {
|
|
32
|
+
const url = new URL(RNX_CHANGELOG_API_URL)
|
|
33
|
+
url.searchParams.set('version', version)
|
|
34
|
+
const response = await fetchImpl(url, {
|
|
35
|
+
headers: { accept: 'application/json' },
|
|
36
|
+
signal: AbortSignal.timeout(5000),
|
|
37
|
+
})
|
|
38
|
+
if (!response.ok) return null
|
|
39
|
+
const payload: unknown = await response.json()
|
|
40
|
+
if (!payload || typeof payload !== 'object') return null
|
|
41
|
+
const entries = Reflect.get(payload, 'entries')
|
|
42
|
+
if (!Array.isArray(entries) || !isChangelogEntry(entries[0])) return null
|
|
43
|
+
return entries[0]
|
|
44
|
+
} catch {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function formatRuntimeReleaseNotes(
|
|
50
|
+
version: string,
|
|
51
|
+
entry: ChangelogEntry | null,
|
|
52
|
+
): string {
|
|
53
|
+
const lines = [`what's new in rnx engine v${version}:`]
|
|
54
|
+
if (!entry || entry.sections.length === 0) {
|
|
55
|
+
lines.push(` release notes are not available yet`)
|
|
56
|
+
} else {
|
|
57
|
+
for (const section of entry.sections) {
|
|
58
|
+
lines.push(` ${section.title}:`)
|
|
59
|
+
for (const item of section.items) {
|
|
60
|
+
lines.push(` • ${item.summary}`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
lines.push(` full changelog: ${RNX_CHANGELOG_URL}`)
|
|
65
|
+
return lines.join('\n')
|
|
66
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readActiveRuntime, shouldSkipPersistentDaemon } from '../src/home-paths'
|
|
2
|
+
import { resolveBridgeWorld } from './ws-bridge'
|
|
3
|
+
|
|
4
|
+
export type RuntimeSummary = {
|
|
5
|
+
primary: string
|
|
6
|
+
installedRuntime: string | null
|
|
7
|
+
isDevBridge: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getRuntimeSummary(): RuntimeSummary {
|
|
11
|
+
const active = readActiveRuntime()
|
|
12
|
+
const dev = shouldSkipPersistentDaemon() ? resolveBridgeWorld() : null
|
|
13
|
+
if (dev?.source === 'dev-bridge') {
|
|
14
|
+
return {
|
|
15
|
+
primary: `engine dev source (:${dev.bridgePort})`,
|
|
16
|
+
installedRuntime: active ? `installed runtime v${active}` : null,
|
|
17
|
+
isDevBridge: true,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
primary: active ? `runtime v${active}` : 'runtime not installed',
|
|
22
|
+
installedRuntime: null,
|
|
23
|
+
isDevBridge: false,
|
|
24
|
+
}
|
|
25
|
+
}
|