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,31 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import {
|
|
3
|
+
closeSync,
|
|
4
|
+
fchmodSync,
|
|
5
|
+
fsyncSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
openSync,
|
|
8
|
+
renameSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from 'node:fs'
|
|
12
|
+
import { dirname } from 'node:path'
|
|
13
|
+
|
|
14
|
+
export function writeSecureJsonAtomic(filepath: string, value: unknown): void {
|
|
15
|
+
mkdirSync(dirname(filepath), { recursive: true })
|
|
16
|
+
const temporary = `${filepath}.tmp-${process.pid}-${randomUUID()}`
|
|
17
|
+
let descriptor: number | null = null
|
|
18
|
+
try {
|
|
19
|
+
descriptor = openSync(temporary, 'wx', 0o600)
|
|
20
|
+
fchmodSync(descriptor, 0o600)
|
|
21
|
+
writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}\n`)
|
|
22
|
+
fsyncSync(descriptor)
|
|
23
|
+
closeSync(descriptor)
|
|
24
|
+
descriptor = null
|
|
25
|
+
renameSync(temporary, filepath)
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (descriptor !== null) closeSync(descriptor)
|
|
28
|
+
rmSync(temporary, { force: true })
|
|
29
|
+
throw error
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// how this CLI re-invokes itself as a child process.
|
|
2
|
+
//
|
|
3
|
+
// two shapes exist and only the build decides which one is running:
|
|
4
|
+
//
|
|
5
|
+
// standalone the bun-compiled executable IS the entry point. it takes the
|
|
6
|
+
// CLI argv directly and there is no script to hand it.
|
|
7
|
+
// entry script every other shape (dev checkout, the repository dist-cli
|
|
8
|
+
// bundle) runs through the javascript runtime executing now,
|
|
9
|
+
// with the script as its first argument.
|
|
10
|
+
//
|
|
11
|
+
// a presence check cannot tell them apart. inside the compiled binary
|
|
12
|
+
// `process.argv[1]` is a path in bun's virtual filesystem (`/$bunfs/root/...`)
|
|
13
|
+
// that passes `existsSync` in-process, so every site that probed argv[1]
|
|
14
|
+
// re-invoked itself as `rnx <bunfs path> <command>` and the child died on
|
|
15
|
+
// `unknown command`. the build-time constant is the only honest signal.
|
|
16
|
+
|
|
17
|
+
import { existsSync } from 'node:fs'
|
|
18
|
+
import { basename, dirname, join } from 'node:path'
|
|
19
|
+
import { fileURLToPath } from 'node:url'
|
|
20
|
+
import { rnxPublicBrand } from '../src/public-brand.ts'
|
|
21
|
+
import { IS_STANDALONE } from './standalone.ts'
|
|
22
|
+
|
|
23
|
+
export interface RnxSelfInvocation {
|
|
24
|
+
/** absolute path to spawn. */
|
|
25
|
+
executable: string
|
|
26
|
+
/** arguments that precede the CLI's own argv. */
|
|
27
|
+
prefixArgs: string[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function sourceEntryPath(): string | null {
|
|
31
|
+
try {
|
|
32
|
+
const candidate = join(dirname(fileURLToPath(import.meta.url)), 'bin.ts')
|
|
33
|
+
return existsSync(candidate) ? candidate : null
|
|
34
|
+
} catch {
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isCliEntry(path: string): boolean {
|
|
40
|
+
const name = basename(path)
|
|
41
|
+
return name === 'bin.ts' || name === 'bin.js'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function rnxSelfInvocation(): RnxSelfInvocation {
|
|
45
|
+
if (IS_STANDALONE) return { executable: process.execPath, prefixArgs: [] }
|
|
46
|
+
const entry = process.argv[1]
|
|
47
|
+
if (entry && isCliEntry(entry)) {
|
|
48
|
+
return { executable: process.execPath, prefixArgs: [entry] }
|
|
49
|
+
}
|
|
50
|
+
const sourceEntry = sourceEntryPath()
|
|
51
|
+
if (sourceEntry) {
|
|
52
|
+
return { executable: process.execPath, prefixArgs: [sourceEntry] }
|
|
53
|
+
}
|
|
54
|
+
if (!entry) {
|
|
55
|
+
throw new Error(`could not locate the ${rnxPublicBrand.commandName} entry script`)
|
|
56
|
+
}
|
|
57
|
+
return { executable: process.execPath, prefixArgs: [entry] }
|
|
58
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// "send to box": the running local app becomes a nano cloud simulator.
|
|
2
|
+
//
|
|
3
|
+
// one function serves both callers. the rail button in the shell reaches it
|
|
4
|
+
// through the local daemon's `/__send-to-box` route, and `rnx box send` calls
|
|
5
|
+
// it directly; both hand it a way to run a bridge command against the sim that
|
|
6
|
+
// is running the app, and both get the same simulator id and URL back.
|
|
7
|
+
|
|
8
|
+
import { isStorageSnapshot } from '../../sootsim-engine/src/preview/storage-snapshot-contract.ts'
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_DEVICE_BY_PLATFORM,
|
|
11
|
+
getRuntimePlatform,
|
|
12
|
+
listDeviceModels,
|
|
13
|
+
} from '../../sootsim-engine/src/settings/devices.ts'
|
|
14
|
+
import { toRNXProductionBundleUrl } from '../src/metro-production-bundle.ts'
|
|
15
|
+
import { createCloudBox } from './cloud-client.ts'
|
|
16
|
+
import type { StorageSnapshot } from '../../sootsim-engine/src/preview/storage-snapshot-contract.ts'
|
|
17
|
+
import type { DeviceModel } from '../../sootsim-engine/src/settings/devices.ts'
|
|
18
|
+
import type { BridgeCommandType } from '../src/bridge-contract.ts'
|
|
19
|
+
|
|
20
|
+
export interface SendToBoxOptions {
|
|
21
|
+
/** runs one bridge command against the sim running the app. */
|
|
22
|
+
send: (command: { type: BridgeCommandType; [key: string]: unknown }) => Promise<unknown>
|
|
23
|
+
authorization: string
|
|
24
|
+
/** the account the box bills. an api key names its own, so it sends none. */
|
|
25
|
+
accountId: string | null
|
|
26
|
+
/** the cloud device model. defaults to the device the sim is running on. */
|
|
27
|
+
device?: DeviceModel
|
|
28
|
+
/**
|
|
29
|
+
* carry the signed-in session into the box. on by default in the shell: the
|
|
30
|
+
* box is the sender's own, and an app that boots signed out is not the app
|
|
31
|
+
* they were looking at.
|
|
32
|
+
*/
|
|
33
|
+
carryAuth: boolean
|
|
34
|
+
boxName?: string | null
|
|
35
|
+
apiOrigin?: string
|
|
36
|
+
simId?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SendToBoxResult {
|
|
40
|
+
boxId: string
|
|
41
|
+
simId: string
|
|
42
|
+
url: string
|
|
43
|
+
carriedAuth: boolean
|
|
44
|
+
warnings: string[]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
48
|
+
return !!value && typeof value === 'object' && !Array.isArray(value)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// the box opens on the device the sender was looking at. a remote simulator is
|
|
52
|
+
// iOS only, so an Android sim sends to the default iPhone rather than refusing.
|
|
53
|
+
function cloudDevice(capturedModel: unknown): DeviceModel {
|
|
54
|
+
const captured = listDeviceModels().find((model) => model === capturedModel)
|
|
55
|
+
return captured && getRuntimePlatform(captured) === 'ios'
|
|
56
|
+
? captured
|
|
57
|
+
: DEFAULT_DEVICE_BY_PLATFORM.ios
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function sendToBox(options: SendToBoxOptions): Promise<SendToBoxResult> {
|
|
61
|
+
const captured: unknown = await options.send({
|
|
62
|
+
type: 'evaluate',
|
|
63
|
+
simId: options.simId,
|
|
64
|
+
code: '(typeof window.__sootsimCaptureBundle === "function") ? window.__sootsimCaptureBundle() : null',
|
|
65
|
+
})
|
|
66
|
+
const bundleUrl = isRecord(captured) ? captured.bundleUrl : null
|
|
67
|
+
if (!isRecord(captured) || typeof bundleUrl !== 'string' || !bundleUrl) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
'send to box needs a simulator running an app: this one has no bundle loaded',
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
// the snapshot is taken before the production bundle is fetched, so the box
|
|
73
|
+
// gets the state the app was in when the button was pressed rather than the
|
|
74
|
+
// state it drifted to while Metro rebuilt.
|
|
75
|
+
const dumped: unknown = await options.send({
|
|
76
|
+
type: 'storageSnapshot',
|
|
77
|
+
simId: options.simId,
|
|
78
|
+
storageSnapshotOptions: { includeCredentials: options.carryAuth },
|
|
79
|
+
})
|
|
80
|
+
if (!isStorageSnapshot(dumped)) {
|
|
81
|
+
throw new Error('the simulator returned a storage snapshot this build cannot read')
|
|
82
|
+
}
|
|
83
|
+
const storage: StorageSnapshot = dumped
|
|
84
|
+
const created = await createCloudBox({
|
|
85
|
+
bundlePath: toRNXProductionBundleUrl(bundleUrl),
|
|
86
|
+
device:
|
|
87
|
+
options.device ??
|
|
88
|
+
cloudDevice(isRecord(captured.deviceSpec) ? captured.deviceSpec.model : null),
|
|
89
|
+
authorization: options.authorization,
|
|
90
|
+
accountId: options.accountId,
|
|
91
|
+
apiOrigin: options.apiOrigin,
|
|
92
|
+
boxName: options.boxName ?? null,
|
|
93
|
+
storage,
|
|
94
|
+
})
|
|
95
|
+
const simId = created.receipt.simulator.simId
|
|
96
|
+
const url = `${created.session.apiOrigin.replace(/\/$/, '')}/sim/${simId}`
|
|
97
|
+
// the local terminal is where an agent driving this checkout reads results,
|
|
98
|
+
// so the id and the URL land there whichever caller asked for the send.
|
|
99
|
+
console.log(` box simulator: ${simId}`)
|
|
100
|
+
console.log(` ${url}`)
|
|
101
|
+
for (const warning of created.warnings) console.warn(` warning: ${warning}`)
|
|
102
|
+
return {
|
|
103
|
+
boxId: created.receipt.boxId,
|
|
104
|
+
simId,
|
|
105
|
+
url,
|
|
106
|
+
carriedAuth: options.carryAuth,
|
|
107
|
+
warnings: created.warnings,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { rnxPublicBrand } from '../src/public-brand'
|
|
5
|
+
|
|
6
|
+
export type SetupPackageManager = 'bun' | 'pnpm' | 'npm'
|
|
7
|
+
|
|
8
|
+
export interface RepositoryOperation {
|
|
9
|
+
command: string
|
|
10
|
+
args: string[]
|
|
11
|
+
description: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RepositorySetupPlan {
|
|
15
|
+
appDir: string
|
|
16
|
+
packageManager: SetupPackageManager
|
|
17
|
+
operations: RepositoryOperation[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface PackageManifest {
|
|
21
|
+
scripts?: Record<string, string>
|
|
22
|
+
devDependencies?: Record<string, string>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function findRepositoryPackageRoot(startDir: string): string | null {
|
|
26
|
+
let current = path.resolve(startDir)
|
|
27
|
+
let packageRoot: string | null = null
|
|
28
|
+
while (true) {
|
|
29
|
+
if (!packageRoot && fs.existsSync(path.join(current, 'package.json'))) {
|
|
30
|
+
packageRoot = current
|
|
31
|
+
}
|
|
32
|
+
if (fs.existsSync(path.join(current, '.git'))) return packageRoot
|
|
33
|
+
const parent = path.dirname(current)
|
|
34
|
+
if (parent === current) return null
|
|
35
|
+
current = parent
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function detectPreferredPackageManager(startDir: string): SetupPackageManager {
|
|
40
|
+
let current = path.resolve(startDir)
|
|
41
|
+
while (true) {
|
|
42
|
+
if (fs.existsSync(path.join(current, 'bun.lock'))) return 'bun'
|
|
43
|
+
if (fs.existsSync(path.join(current, 'bun.lockb'))) return 'bun'
|
|
44
|
+
if (fs.existsSync(path.join(current, 'pnpm-lock.yaml'))) return 'pnpm'
|
|
45
|
+
if (fs.existsSync(path.join(current, 'package-lock.json'))) return 'npm'
|
|
46
|
+
const parent = path.dirname(current)
|
|
47
|
+
if (parent === current) return 'npm'
|
|
48
|
+
current = parent
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function planRepositorySetup(input: {
|
|
53
|
+
appDir: string
|
|
54
|
+
cliVersion: string
|
|
55
|
+
}): RepositorySetupPlan {
|
|
56
|
+
const appDir = path.resolve(input.appDir)
|
|
57
|
+
const manifest = readManifest(path.join(appDir, 'package.json'))
|
|
58
|
+
const expectedScript = `${rnxPublicBrand.commandName} open`
|
|
59
|
+
const existingScript = manifest.scripts?.[rnxPublicBrand.commandName]
|
|
60
|
+
if (existingScript && existingScript !== expectedScript) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`package.json already defines scripts.${rnxPublicBrand.commandName} as ${JSON.stringify(existingScript)}; resolve that conflict before setup`,
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const packageManager = detectPreferredPackageManager(appDir)
|
|
67
|
+
const operations: RepositoryOperation[] = []
|
|
68
|
+
if (manifest.devDependencies?.[rnxPublicBrand.packageName] !== input.cliVersion) {
|
|
69
|
+
operations.push(addDependencyOperation(packageManager, input.cliVersion))
|
|
70
|
+
}
|
|
71
|
+
if (existingScript !== expectedScript) {
|
|
72
|
+
operations.push(scriptOperation(packageManager))
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
appDir,
|
|
76
|
+
packageManager,
|
|
77
|
+
operations,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function applyRepositorySetup(
|
|
82
|
+
plan: RepositorySetupPlan,
|
|
83
|
+
options: { dryRun: boolean; json: boolean },
|
|
84
|
+
): Promise<void> {
|
|
85
|
+
for (const operation of plan.operations) {
|
|
86
|
+
if (options.dryRun) continue
|
|
87
|
+
await runOperation(operation, plan.appDir, options.json)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function formatRepositoryOperations(plan: RepositorySetupPlan): string[] {
|
|
92
|
+
return plan.operations.map(
|
|
93
|
+
(operation) => `${operation.command} ${operation.args.map(quoteArg).join(' ')}`,
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readManifest(packagePath: string): PackageManifest {
|
|
98
|
+
let parsed: unknown
|
|
99
|
+
try {
|
|
100
|
+
parsed = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
103
|
+
throw new Error(`could not read ${packagePath}: ${message}`)
|
|
104
|
+
}
|
|
105
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
106
|
+
throw new Error(`${packagePath} must contain a JSON object`)
|
|
107
|
+
}
|
|
108
|
+
const scripts = readStringRecord(Reflect.get(parsed, 'scripts'))
|
|
109
|
+
const devDependencies = readStringRecord(Reflect.get(parsed, 'devDependencies'))
|
|
110
|
+
return {
|
|
111
|
+
...(scripts ? { scripts } : {}),
|
|
112
|
+
...(devDependencies ? { devDependencies } : {}),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readStringRecord(value: unknown): Record<string, string> | null {
|
|
117
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null
|
|
118
|
+
const entries = Object.entries(value).filter(
|
|
119
|
+
(entry): entry is [string, string] => typeof entry[1] === 'string',
|
|
120
|
+
)
|
|
121
|
+
return Object.fromEntries(entries)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function addDependencyOperation(
|
|
125
|
+
packageManager: SetupPackageManager,
|
|
126
|
+
version: string,
|
|
127
|
+
): RepositoryOperation {
|
|
128
|
+
const spec = `${rnxPublicBrand.packageName}@${version}`
|
|
129
|
+
switch (packageManager) {
|
|
130
|
+
case 'bun':
|
|
131
|
+
return {
|
|
132
|
+
command: 'bun',
|
|
133
|
+
args: ['add', '--dev', '--exact', spec],
|
|
134
|
+
description: `install ${spec} as an exact dev dependency`,
|
|
135
|
+
}
|
|
136
|
+
case 'pnpm':
|
|
137
|
+
return {
|
|
138
|
+
command: 'pnpm',
|
|
139
|
+
args: ['add', '--save-dev', '--save-exact', spec],
|
|
140
|
+
description: `install ${spec} as an exact dev dependency`,
|
|
141
|
+
}
|
|
142
|
+
case 'npm':
|
|
143
|
+
return {
|
|
144
|
+
command: 'npm',
|
|
145
|
+
args: ['install', '--save-dev', '--save-exact', spec],
|
|
146
|
+
description: `install ${spec} as an exact dev dependency`,
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function quoteArg(value: string): string {
|
|
152
|
+
return /^[a-zA-Z0-9_./@=-]+$/.test(value) ? value : JSON.stringify(value)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function runOperation(
|
|
156
|
+
operation: RepositoryOperation,
|
|
157
|
+
cwd: string,
|
|
158
|
+
json: boolean,
|
|
159
|
+
): Promise<void> {
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
const child = spawn(operation.command, operation.args, {
|
|
162
|
+
cwd,
|
|
163
|
+
stdio: json ? ['ignore', 2, 'inherit'] : 'inherit',
|
|
164
|
+
})
|
|
165
|
+
child.once('error', reject)
|
|
166
|
+
child.once('exit', (code) => {
|
|
167
|
+
if (code === 0) resolve()
|
|
168
|
+
else reject(new Error(`${operation.command} exited with code ${code ?? 'unknown'}`))
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function scriptOperation(packageManager: SetupPackageManager): RepositoryOperation {
|
|
174
|
+
return {
|
|
175
|
+
command: packageManager,
|
|
176
|
+
args:
|
|
177
|
+
packageManager === 'bun'
|
|
178
|
+
? [
|
|
179
|
+
'pm',
|
|
180
|
+
'pkg',
|
|
181
|
+
'set',
|
|
182
|
+
`scripts.${rnxPublicBrand.commandName}=${rnxPublicBrand.commandName} open`,
|
|
183
|
+
]
|
|
184
|
+
: [
|
|
185
|
+
'pkg',
|
|
186
|
+
'set',
|
|
187
|
+
`scripts.${rnxPublicBrand.commandName}=${rnxPublicBrand.commandName} open`,
|
|
188
|
+
],
|
|
189
|
+
description: `add the \`${rnxPublicBrand.commandName}\` package script`,
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RNX_CLOUD_SESSION_ENV,
|
|
3
|
+
RNX_SHELL_UPDATE_SET_PREFIX,
|
|
4
|
+
RNX_SHELL_UPDATE_UNSET,
|
|
5
|
+
} from './cloud-session'
|
|
6
|
+
|
|
7
|
+
export function renderRnxShellInit(shell: string): string {
|
|
8
|
+
if (shell !== 'bash' && shell !== 'zsh') {
|
|
9
|
+
throw new Error('RNX shell integration supports bash and zsh')
|
|
10
|
+
}
|
|
11
|
+
return `rnx() {
|
|
12
|
+
local -a _rnx_shell_args
|
|
13
|
+
local _rnx_shell_command _rnx_shell_create _rnx_shell_update
|
|
14
|
+
local _rnx_shell_session _rnx_shell_status _rnx_shell_traced
|
|
15
|
+
_rnx_shell_args=("$@")
|
|
16
|
+
_rnx_shell_command=''
|
|
17
|
+
_rnx_shell_create=0
|
|
18
|
+
while [ "$#" -gt 0 ]; do
|
|
19
|
+
case "$1" in
|
|
20
|
+
-h|--help|-V|--version|-v|--verbose|--headless) shift ;;
|
|
21
|
+
-p|--port|-d|--device|-t|--theme|--driver|--sim|--session|--tab)
|
|
22
|
+
if [ "$#" -ge 2 ]; then shift 2; else shift; fi
|
|
23
|
+
;;
|
|
24
|
+
--) break ;;
|
|
25
|
+
-*) shift ;;
|
|
26
|
+
*) _rnx_shell_command="$1"; shift; break ;;
|
|
27
|
+
esac
|
|
28
|
+
done
|
|
29
|
+
case "$_rnx_shell_command" in
|
|
30
|
+
claim|close) ;;
|
|
31
|
+
ios)
|
|
32
|
+
for _rnx_shell_session in "$@"; do
|
|
33
|
+
if [ "$_rnx_shell_session" = '--remote' ]; then
|
|
34
|
+
_rnx_shell_create=1
|
|
35
|
+
break
|
|
36
|
+
fi
|
|
37
|
+
done
|
|
38
|
+
if [ "$_rnx_shell_create" -ne 1 ]; then
|
|
39
|
+
command rnx "\${_rnx_shell_args[@]}"
|
|
40
|
+
return $?
|
|
41
|
+
fi
|
|
42
|
+
;;
|
|
43
|
+
*)
|
|
44
|
+
command rnx "\${_rnx_shell_args[@]}"
|
|
45
|
+
return $?
|
|
46
|
+
;;
|
|
47
|
+
esac
|
|
48
|
+
|
|
49
|
+
if { [ -n "\${BASH_VERSION:-}" ] && [ "\${BASH_SUBSHELL:-0}" -ne 0 ]; } ||
|
|
50
|
+
{ [ -n "\${ZSH_VERSION:-}" ] && [ "\${ZSH_SUBSHELL:-0}" -ne 0 ]; }; then
|
|
51
|
+
printf '%s\\n' 'rnx remote simulator lifecycle commands must run directly in the current shell, not in a pipeline or command substitution' >&2
|
|
52
|
+
return 1
|
|
53
|
+
fi
|
|
54
|
+
if ( : >&9 ) 2>/dev/null; then
|
|
55
|
+
printf '%s\\n' 'rnx remote simulator shell integration requires file descriptor 9 to be unused' >&2
|
|
56
|
+
return 1
|
|
57
|
+
fi
|
|
58
|
+
if ! ( export ${RNX_CLOUD_SESSION_ENV}=__rnx_shell_probe && unset ${RNX_CLOUD_SESSION_ENV} ) 2>/dev/null; then
|
|
59
|
+
printf '%s\\n' 'rnx remote simulator shell integration cannot update RNX_CLOUD_SESSION in this shell' >&2
|
|
60
|
+
return 1
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
case "$-" in
|
|
64
|
+
*x*) _rnx_shell_traced=1; set +x ;;
|
|
65
|
+
*) _rnx_shell_traced=0 ;;
|
|
66
|
+
esac
|
|
67
|
+
exec 9>&1
|
|
68
|
+
_rnx_shell_update="$(RNX_SHELL_UPDATE_FD=3 command rnx "\${_rnx_shell_args[@]}" 3>&1 1>&9)"
|
|
69
|
+
_rnx_shell_status=$?
|
|
70
|
+
exec 9>&-
|
|
71
|
+
|
|
72
|
+
if [ "$_rnx_shell_status" -eq 0 ]; then
|
|
73
|
+
case "$_rnx_shell_update" in
|
|
74
|
+
${RNX_SHELL_UPDATE_SET_PREFIX}*)
|
|
75
|
+
_rnx_shell_session="\${_rnx_shell_update#${RNX_SHELL_UPDATE_SET_PREFIX}}"
|
|
76
|
+
case "$_rnx_shell_session" in
|
|
77
|
+
''|*$'\\n'*) _rnx_shell_status=1 ;;
|
|
78
|
+
*) export ${RNX_CLOUD_SESSION_ENV}="$_rnx_shell_session" || _rnx_shell_status=$? ;;
|
|
79
|
+
esac
|
|
80
|
+
;;
|
|
81
|
+
${RNX_SHELL_UPDATE_UNSET})
|
|
82
|
+
unset ${RNX_CLOUD_SESSION_ENV} || _rnx_shell_status=$?
|
|
83
|
+
;;
|
|
84
|
+
*) _rnx_shell_status=1 ;;
|
|
85
|
+
esac
|
|
86
|
+
if [ "$_rnx_shell_status" -ne 0 ]; then
|
|
87
|
+
printf '%s\\n' 'rnx refused an invalid shell session update' >&2
|
|
88
|
+
elif [ "$_rnx_shell_create" -eq 1 ]; then
|
|
89
|
+
command rnx __cloud-confirm
|
|
90
|
+
_rnx_shell_status=$?
|
|
91
|
+
fi
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
if [ "$_rnx_shell_traced" -eq 1 ]; then set -x; fi
|
|
95
|
+
return "$_rnx_shell_status"
|
|
96
|
+
}
|
|
97
|
+
`
|
|
98
|
+
}
|
package/cli/telemetry.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { diagnosticsAllowedForCurrentProcess } from './privacy'
|
|
2
|
+
|
|
3
|
+
type PackageIncompatibility = {
|
|
4
|
+
kind: 'package_incompatibility'
|
|
5
|
+
packageName: string
|
|
6
|
+
packageVersion: string
|
|
7
|
+
unsupportedApi?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type ErrorShape = {
|
|
11
|
+
kind: 'error_shape'
|
|
12
|
+
errorClass: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type CliDiagnostic = PackageIncompatibility | ErrorShape
|
|
16
|
+
|
|
17
|
+
type Config = { apiKey: string; host: string }
|
|
18
|
+
|
|
19
|
+
function loadConfig(): Config | null {
|
|
20
|
+
if (!diagnosticsAllowedForCurrentProcess()) return null
|
|
21
|
+
const apiKey =
|
|
22
|
+
process.env.CONTRAST_POSTHOG_API_KEY || process.env.VITE_POSTHOG_API_KEY || ''
|
|
23
|
+
if (!apiKey) return null
|
|
24
|
+
const host =
|
|
25
|
+
process.env.CONTRAST_POSTHOG_HOST ||
|
|
26
|
+
process.env.VITE_POSTHOG_HOST ||
|
|
27
|
+
'https://us.i.posthog.com'
|
|
28
|
+
return { apiKey, host: host.replace(/\/+$/, '') }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function bounded(value: string, max: number): string {
|
|
32
|
+
return value.trim().slice(0, max)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function buildCliDiagnosticEvent(diagnostic: CliDiagnostic) {
|
|
36
|
+
const properties =
|
|
37
|
+
diagnostic.kind === 'package_incompatibility'
|
|
38
|
+
? {
|
|
39
|
+
package_name: bounded(diagnostic.packageName, 160),
|
|
40
|
+
package_version: bounded(diagnostic.packageVersion, 80),
|
|
41
|
+
...(diagnostic.unsupportedApi
|
|
42
|
+
? { unsupported_api: bounded(diagnostic.unsupportedApi, 160) }
|
|
43
|
+
: {}),
|
|
44
|
+
}
|
|
45
|
+
: { error_class: bounded(diagnostic.errorClass, 160) }
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
event: diagnostic.kind,
|
|
49
|
+
distinct_id: 'rnx-cli-diagnostic',
|
|
50
|
+
properties,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const queue: CliDiagnostic[] = []
|
|
55
|
+
|
|
56
|
+
export function trackCliDiagnostic(diagnostic: CliDiagnostic): void {
|
|
57
|
+
if (!diagnosticsAllowedForCurrentProcess()) return
|
|
58
|
+
queue.push(diagnostic)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function trackCliError(error: unknown): void {
|
|
62
|
+
const errorClass = error instanceof Error ? error.constructor.name : typeof error
|
|
63
|
+
trackCliDiagnostic({ kind: 'error_shape', errorClass })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function flushCliTelemetry(): Promise<void> {
|
|
67
|
+
if (queue.length === 0) return
|
|
68
|
+
const config = loadConfig()
|
|
69
|
+
if (!config) {
|
|
70
|
+
queue.length = 0
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
const batch = queue.splice(0, queue.length).map(buildCliDiagnosticEvent)
|
|
74
|
+
try {
|
|
75
|
+
await fetch(`${config.host}/batch/`, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: { 'content-type': 'application/json' },
|
|
78
|
+
body: JSON.stringify({ api_key: config.apiKey, batch }),
|
|
79
|
+
signal: AbortSignal.timeout(2_000),
|
|
80
|
+
})
|
|
81
|
+
} catch {
|
|
82
|
+
// diagnostics never change a command's result.
|
|
83
|
+
}
|
|
84
|
+
}
|
package/cli/ui.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const RESET = '\x1b[0m'
|
|
2
|
+
const BOLD = '\x1b[1m'
|
|
3
|
+
const DIM = '\x1b[2m'
|
|
4
|
+
const BRAND_BLUE = '\x1b[38;2;42;95;255m'
|
|
5
|
+
const GREEN = '\x1b[38;5;84m'
|
|
6
|
+
const RED = '\x1b[38;5;203m'
|
|
7
|
+
|
|
8
|
+
function paint(code: string, value: string): string {
|
|
9
|
+
if (!process.stdout.isTTY || process.env.NO_COLOR !== undefined) return value
|
|
10
|
+
return `${code}${value}${RESET}`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function accent(value: string): string {
|
|
14
|
+
return paint(BRAND_BLUE, value)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function bold(value: string): string {
|
|
18
|
+
return paint(BOLD, value)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function muted(value: string): string {
|
|
22
|
+
return paint(DIM, value)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function success(value: string): string {
|
|
26
|
+
return paint(GREEN, value)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function failure(value: string): string {
|
|
30
|
+
return paint(RED, value)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function printBrand(subtitle?: string): void {
|
|
34
|
+
console.log()
|
|
35
|
+
console.log(` ${accent('◤◢')} ${bold('rnx')}`)
|
|
36
|
+
if (subtitle) console.log(` ${muted(subtitle)}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function printPromptBlock(title: string, description: string[]): void {
|
|
40
|
+
console.log(`\n ${accent('◆')} ${bold(title)}`)
|
|
41
|
+
for (const line of description) console.log(` ${muted(line)}`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function withSpinner<T>(label: string, run: () => Promise<T>): Promise<T> {
|
|
45
|
+
if (!process.stdout.isTTY) {
|
|
46
|
+
console.log(` ${label}...`)
|
|
47
|
+
return run()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
51
|
+
let index = 0
|
|
52
|
+
const draw = () => {
|
|
53
|
+
process.stdout.write(`\r\x1b[2K ${accent(frames[index++ % frames.length])} ${label}`)
|
|
54
|
+
}
|
|
55
|
+
draw()
|
|
56
|
+
const timer = setInterval(draw, 80)
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const result = await run()
|
|
60
|
+
clearInterval(timer)
|
|
61
|
+
process.stdout.write(`\r\x1b[2K ${success('✓')} ${label}\n`)
|
|
62
|
+
return result
|
|
63
|
+
} catch (error) {
|
|
64
|
+
clearInterval(timer)
|
|
65
|
+
process.stdout.write(`\r\x1b[2K ${failure('×')} ${label}\n`)
|
|
66
|
+
throw error
|
|
67
|
+
}
|
|
68
|
+
}
|