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,357 @@
|
|
|
1
|
+
import { exec } from 'node:child_process'
|
|
2
|
+
import { randomBytes } from 'node:crypto'
|
|
3
|
+
import http from 'node:http'
|
|
4
|
+
import { createInterface } from 'node:readline/promises'
|
|
5
|
+
import {
|
|
6
|
+
refreshSharedDesktopAuthSession,
|
|
7
|
+
sharedDesktopAuthAccounts,
|
|
8
|
+
writeSharedDesktopAuthSession,
|
|
9
|
+
type SharedDesktopAuthSession,
|
|
10
|
+
} from '../../src/auth/shared-session'
|
|
11
|
+
import { rnxPublicBrand } from '../../src/public-brand'
|
|
12
|
+
import { rnxExit } from '../run-rnx'
|
|
13
|
+
|
|
14
|
+
const REMOTE_DEFAULT_ORIGIN = process.env.RNX_AUTH_ORIGIN || 'https://contrast.dev'
|
|
15
|
+
|
|
16
|
+
type LoginResult =
|
|
17
|
+
| { ok: true; token: string; userId?: string; email?: string }
|
|
18
|
+
| { ok: false; error: string }
|
|
19
|
+
|
|
20
|
+
function resolveDefaultAuthOrigin(explicitOrigin?: string) {
|
|
21
|
+
if (explicitOrigin) return explicitOrigin
|
|
22
|
+
if (process.env.RNX_AUTH_ORIGIN) return process.env.RNX_AUTH_ORIGIN
|
|
23
|
+
if (process.env.RNX_UPLOAD_ORIGIN) return process.env.RNX_UPLOAD_ORIGIN
|
|
24
|
+
return REMOTE_DEFAULT_ORIGIN
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function printHelp() {
|
|
28
|
+
console.log(`
|
|
29
|
+
rnx login — sign in so uploads can attach to your account
|
|
30
|
+
|
|
31
|
+
usage:
|
|
32
|
+
rnx login [--origin <url>] [--account <name-or-id>]
|
|
33
|
+
|
|
34
|
+
options:
|
|
35
|
+
--origin <url> auth host (default: ${REMOTE_DEFAULT_ORIGIN})
|
|
36
|
+
override with RNX_AUTH_ORIGIN env var
|
|
37
|
+
--account <name-or-id> the account remote simulators and boxes bill to.
|
|
38
|
+
one account is chosen for you; several ask.
|
|
39
|
+
-h, --help
|
|
40
|
+
`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// a session token names the person, not an account. the account that pays for
|
|
44
|
+
// cloud work is chosen here, once, and stored on the session; a person with one
|
|
45
|
+
// account never sees the question.
|
|
46
|
+
async function chooseAccount(
|
|
47
|
+
session: SharedDesktopAuthSession,
|
|
48
|
+
requested: string | undefined,
|
|
49
|
+
): Promise<{ id: string; name: string } | null> {
|
|
50
|
+
const accounts = sharedDesktopAuthAccounts(session.user)
|
|
51
|
+
if (requested !== undefined) {
|
|
52
|
+
const wanted = requested.trim().toLowerCase()
|
|
53
|
+
const match = accounts.find(
|
|
54
|
+
(account) =>
|
|
55
|
+
account.id === requested.trim() || account.name.toLowerCase() === wanted,
|
|
56
|
+
)
|
|
57
|
+
if (!match) {
|
|
58
|
+
console.error(
|
|
59
|
+
` no account named ${JSON.stringify(requested)}. your accounts:\n` +
|
|
60
|
+
accounts.map((account) => ` ${account.name} (${account.id})`).join('\n'),
|
|
61
|
+
)
|
|
62
|
+
rnxExit(1)
|
|
63
|
+
}
|
|
64
|
+
return match
|
|
65
|
+
}
|
|
66
|
+
if (accounts.length <= 1) return accounts[0] ?? null
|
|
67
|
+
if (!process.stdin.isTTY) {
|
|
68
|
+
console.error(
|
|
69
|
+
' this sign-in belongs to several accounts; rerun with --account <name> to choose the one to bill:\n' +
|
|
70
|
+
accounts.map((account) => ` ${account.name} (${account.id})`).join('\n'),
|
|
71
|
+
)
|
|
72
|
+
rnxExit(1)
|
|
73
|
+
}
|
|
74
|
+
console.log(' which account should remote simulators and boxes bill to?')
|
|
75
|
+
accounts.forEach((account, index) => console.log(` ${index + 1}. ${account.name}`))
|
|
76
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout })
|
|
77
|
+
try {
|
|
78
|
+
for (;;) {
|
|
79
|
+
const answer = (await prompt.question(` account [1-${accounts.length}]: `)).trim()
|
|
80
|
+
const chosen = accounts[Number(answer) - 1]
|
|
81
|
+
if (chosen && /^\d+$/.test(answer)) return chosen
|
|
82
|
+
console.log(` enter a number from 1 to ${accounts.length}`)
|
|
83
|
+
}
|
|
84
|
+
} finally {
|
|
85
|
+
prompt.close()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function finishLogin(
|
|
90
|
+
origin: string,
|
|
91
|
+
requestedAccount: string | undefined,
|
|
92
|
+
fallbackLabel: string | undefined,
|
|
93
|
+
suffix: string,
|
|
94
|
+
) {
|
|
95
|
+
const refreshed = await refreshSharedDesktopAuthSession(origin)
|
|
96
|
+
const account = refreshed ? await chooseAccount(refreshed, requestedAccount) : null
|
|
97
|
+
if (refreshed && account) {
|
|
98
|
+
writeSharedDesktopAuthSession({ ...refreshed, accountId: account.id })
|
|
99
|
+
}
|
|
100
|
+
const label = refreshed?.user?.email || fallbackLabel || refreshed?.user?.id
|
|
101
|
+
console.log(
|
|
102
|
+
` signed in${label ? ` as ${label}` : ''}${account ? ` (${account.name})` : ''}${suffix}`,
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function extractStringFlag(args: string[], name: string): string | undefined {
|
|
107
|
+
const idx = args.findIndex((a) => a === name)
|
|
108
|
+
if (idx < 0) return undefined
|
|
109
|
+
const value = args[idx + 1]
|
|
110
|
+
args.splice(idx, 2)
|
|
111
|
+
return value
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function openBrowser(url: string) {
|
|
115
|
+
const command =
|
|
116
|
+
process.platform === 'darwin'
|
|
117
|
+
? `open "${url}"`
|
|
118
|
+
: process.platform === 'win32'
|
|
119
|
+
? `start "" "${url}"`
|
|
120
|
+
: `xdg-open "${url}"`
|
|
121
|
+
|
|
122
|
+
exec(command, (err) => {
|
|
123
|
+
if (!err) return
|
|
124
|
+
console.log(' could not open browser automatically.')
|
|
125
|
+
console.log(` open this URL manually:\n ${url}\n`)
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function renderCallbackPage(body: string) {
|
|
130
|
+
return `<!doctype html>
|
|
131
|
+
<html>
|
|
132
|
+
<body style="font-family:system-ui;background:#000;color:#fff;display:flex;align-items:center;justify-content:center;height:100vh;margin:0">
|
|
133
|
+
<div style="max-width:480px;text-align:center">
|
|
134
|
+
<h1 style="font-size:28px;margin:0 0 12px">${rnxPublicBrand.name}</h1>
|
|
135
|
+
<p style="color:#ccc">${body}</p>
|
|
136
|
+
</div>
|
|
137
|
+
</body>
|
|
138
|
+
</html>`
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function runDevLogin(origin: string): Promise<LoginResult> {
|
|
142
|
+
try {
|
|
143
|
+
const res = await fetch(`${origin.replace(/\/$/, '')}/api/dev-login`, {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
})
|
|
146
|
+
if (res.status === 403) {
|
|
147
|
+
return { ok: false, error: 'local dev-login unavailable' }
|
|
148
|
+
}
|
|
149
|
+
if (!res.ok) {
|
|
150
|
+
const text = await res.text().catch(() => '')
|
|
151
|
+
return { ok: false, error: `dev-login ${res.status}: ${text}` }
|
|
152
|
+
}
|
|
153
|
+
const body = (await res.json()) as {
|
|
154
|
+
ok?: boolean
|
|
155
|
+
token?: string
|
|
156
|
+
email?: string
|
|
157
|
+
user?: { id?: string }
|
|
158
|
+
}
|
|
159
|
+
if (!body.token) {
|
|
160
|
+
return { ok: false, error: 'dev-login response missing token' }
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
ok: true,
|
|
164
|
+
token: body.token,
|
|
165
|
+
userId: body.user?.id,
|
|
166
|
+
email: body.email,
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
error: err instanceof Error ? err.message : 'dev-login fetch failed',
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isLocalOrigin(origin: string): boolean {
|
|
177
|
+
try {
|
|
178
|
+
const hostname = new URL(origin).hostname.replace(/^\[|\]$/g, '').toLowerCase()
|
|
179
|
+
if (hostname === 'localhost') return true
|
|
180
|
+
if (/^127(?:\.\d{1,3}){3}$/.test(hostname)) return true
|
|
181
|
+
if (hostname === '::1' || hostname === '0.0.0.0') return true
|
|
182
|
+
if (hostname.endsWith('.local')) return true
|
|
183
|
+
if (hostname.endsWith('.localhost')) return true
|
|
184
|
+
// VM↔host bridge aliases — orb, docker, lima, multipass all forward the
|
|
185
|
+
// host on a well-known name (IPv4 or IPv6) so a VM-internal `rnx login`
|
|
186
|
+
// can still take the dev-login fast path against the host's Contrast dev
|
|
187
|
+
// server. without this the CLI falls through to a browser callback that
|
|
188
|
+
// is unreachable from a headless VM.
|
|
189
|
+
if (hostname === 'host.containers.internal') return true
|
|
190
|
+
if (hostname === 'host.docker.internal') return true
|
|
191
|
+
if (hostname === 'host.orb.internal') return true
|
|
192
|
+
if (hostname === 'host.lima.internal') return true
|
|
193
|
+
// orb's IPv6 form of host.orb.internal: fd07:b51a:cc66:f0::fe (always
|
|
194
|
+
// this fixed address; it's a stable orb ULA assignment, see orb docs).
|
|
195
|
+
if (hostname === 'fd07:b51a:cc66:f0::fe') return true
|
|
196
|
+
// lima's qemu (slirp) backend does NOT resolve host.lima.internal — the
|
|
197
|
+
// host is the fixed slirp gateway 192.168.5.2. a lima/qemu user therefore
|
|
198
|
+
// targets the raw gateway IP, which would otherwise miss the dev-login fast
|
|
199
|
+
// path and fall through to the headless-broken browser callback. recognize
|
|
200
|
+
// it (and multipass's default bridge .1 gateways) so dev-login still fires.
|
|
201
|
+
// safe: no-device dev-login requests are accepted only in local dev.
|
|
202
|
+
if (hostname === '192.168.5.2') return true
|
|
203
|
+
if (/^192\.168\.(?:64|139)\.1$/.test(hostname)) return true
|
|
204
|
+
return false
|
|
205
|
+
} catch {
|
|
206
|
+
return false
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export async function runLogin(args: string[]) {
|
|
211
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
212
|
+
printHelp()
|
|
213
|
+
rnxExit(0)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const mutableArgs = [...args]
|
|
217
|
+
const origin = resolveDefaultAuthOrigin(extractStringFlag(mutableArgs, '--origin'))
|
|
218
|
+
const requestedAccount = extractStringFlag(mutableArgs, '--account')
|
|
219
|
+
|
|
220
|
+
// in dev (localhost), skip the browser and sign in as the shared dev
|
|
221
|
+
// user via POST /api/dev-login. the no-device contract is local-only, so this
|
|
222
|
+
// path is safe to take unconditionally against local hosts.
|
|
223
|
+
if (isLocalOrigin(origin)) {
|
|
224
|
+
console.log(` detected local origin (${origin}) — signing in as dev user`)
|
|
225
|
+
const devResult = await runDevLogin(origin)
|
|
226
|
+
if (devResult.ok) {
|
|
227
|
+
writeSharedDesktopAuthSession({
|
|
228
|
+
token: devResult.token,
|
|
229
|
+
user: devResult.userId ? { id: devResult.userId, email: devResult.email } : null,
|
|
230
|
+
origin,
|
|
231
|
+
source: 'cli',
|
|
232
|
+
})
|
|
233
|
+
await finishLogin(
|
|
234
|
+
origin,
|
|
235
|
+
requestedAccount,
|
|
236
|
+
devResult.email || devResult.userId,
|
|
237
|
+
' (dev)',
|
|
238
|
+
)
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
console.log(` dev-login not available (${devResult.error}); falling back to browser`)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const state = randomBytes(16).toString('hex')
|
|
245
|
+
|
|
246
|
+
// the browser round-trip needs human action (github's authorize screen on
|
|
247
|
+
// first sign-in), so the wait is generous, but it must not be infinite: a
|
|
248
|
+
// closed tab, denied consent, or blocked popup used to hang the CLI forever.
|
|
249
|
+
const LOGIN_TIMEOUT_MS = 5 * 60 * 1000
|
|
250
|
+
|
|
251
|
+
const result = await new Promise<LoginResult>((resolve) => {
|
|
252
|
+
let timer: ReturnType<typeof setTimeout>
|
|
253
|
+
let settled = false
|
|
254
|
+
const finish = (r: LoginResult) => {
|
|
255
|
+
if (settled) return
|
|
256
|
+
settled = true
|
|
257
|
+
clearTimeout(timer)
|
|
258
|
+
resolve(r)
|
|
259
|
+
server.close()
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const server = http.createServer((req, res) => {
|
|
263
|
+
try {
|
|
264
|
+
const url = new URL(req.url || '/', 'http://127.0.0.1')
|
|
265
|
+
const returnedState = url.searchParams.get('state')
|
|
266
|
+
const token = url.searchParams.get('token')
|
|
267
|
+
const email = url.searchParams.get('email') || undefined
|
|
268
|
+
const userId = url.searchParams.get('userId') || undefined
|
|
269
|
+
const error = url.searchParams.get('error')
|
|
270
|
+
|
|
271
|
+
// stray localhost traffic (port scanners, favicon fetches, browser
|
|
272
|
+
// prefetch) must not abort the login. only a request carrying our
|
|
273
|
+
// state is the real callback; rfc 8252 loopback servers ignore the
|
|
274
|
+
// rest. aborting here used to kill the login mid-wait.
|
|
275
|
+
if (returnedState !== state) {
|
|
276
|
+
res.statusCode = 404
|
|
277
|
+
res.end('not found')
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
res.setHeader('content-type', 'text/html; charset=utf-8')
|
|
282
|
+
|
|
283
|
+
if (error) {
|
|
284
|
+
res.end(renderCallbackPage(`sign-in failed: ${error}`))
|
|
285
|
+
finish({ ok: false, error })
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!token) {
|
|
290
|
+
res.end(
|
|
291
|
+
renderCallbackPage('missing token. close this tab and retry `rnx login`.'),
|
|
292
|
+
)
|
|
293
|
+
finish({ ok: false, error: 'missing token' })
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
res.end(renderCallbackPage('sign-in complete. you can return to the terminal.'))
|
|
298
|
+
finish({ ok: true, token, email, userId })
|
|
299
|
+
} catch (err) {
|
|
300
|
+
res.statusCode = 500
|
|
301
|
+
res.end(renderCallbackPage('sign-in callback failed. retry `rnx login`.'))
|
|
302
|
+
finish({
|
|
303
|
+
ok: false,
|
|
304
|
+
error: err instanceof Error ? err.message : 'callback failed',
|
|
305
|
+
})
|
|
306
|
+
}
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
timer = setTimeout(() => {
|
|
310
|
+
finish({
|
|
311
|
+
ok: false,
|
|
312
|
+
error:
|
|
313
|
+
'timed out waiting for the browser sign-in (5 minutes). rerun `rnx login` and complete the github authorize screen if it appears.',
|
|
314
|
+
})
|
|
315
|
+
}, LOGIN_TIMEOUT_MS)
|
|
316
|
+
|
|
317
|
+
server.listen(0, '127.0.0.1', () => {
|
|
318
|
+
const address = server.address()
|
|
319
|
+
if (!address || typeof address === 'string') {
|
|
320
|
+
finish({ ok: false, error: 'failed to bind callback server' })
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const url = new URL(`${origin.replace(/\/$/, '')}/auth/rnx-cli`)
|
|
325
|
+
url.searchParams.set('state', state)
|
|
326
|
+
url.searchParams.set('port', String(address.port))
|
|
327
|
+
url.searchParams.set('contrast', origin)
|
|
328
|
+
|
|
329
|
+
console.log(' opening browser for rnx login...')
|
|
330
|
+
console.log(
|
|
331
|
+
' first time here? github will show an authorize screen. approve it to continue.',
|
|
332
|
+
)
|
|
333
|
+
console.log(` waiting up to 5 minutes for the browser (ctrl+c to cancel)`)
|
|
334
|
+
console.log(` if nothing opens, visit:\n ${url.toString()}\n`)
|
|
335
|
+
openBrowser(url.toString())
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
if (!result.ok) {
|
|
340
|
+
console.error(` login failed: ${result.error}`)
|
|
341
|
+
rnxExit(1)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
writeSharedDesktopAuthSession({
|
|
345
|
+
token: result.token,
|
|
346
|
+
user: result.userId
|
|
347
|
+
? {
|
|
348
|
+
id: result.userId,
|
|
349
|
+
email: result.email,
|
|
350
|
+
}
|
|
351
|
+
: null,
|
|
352
|
+
origin,
|
|
353
|
+
source: 'cli',
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
await finishLogin(origin, requestedAccount, result.email || result.userId, '')
|
|
357
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearSharedDesktopAuthSession,
|
|
3
|
+
readSharedDesktopAuthSession,
|
|
4
|
+
} from '../../src/auth/shared-session'
|
|
5
|
+
|
|
6
|
+
export async function runLogout() {
|
|
7
|
+
const current = readSharedDesktopAuthSession()
|
|
8
|
+
if (!current?.token) {
|
|
9
|
+
console.log(' not signed in')
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
await fetch(`${current.origin.replace(/\/$/, '')}/api/auth/sign-out`, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: { authorization: `Bearer ${current.token}` },
|
|
17
|
+
})
|
|
18
|
+
} catch {
|
|
19
|
+
// best-effort only
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
clearSharedDesktopAuthSession()
|
|
23
|
+
console.log(' signed out')
|
|
24
|
+
}
|