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,540 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { isDisposableBrowserCacheDir } from './browser-cache'
|
|
4
|
+
import { ensureRnxHome, readLiveRuntimeVersions, rnxHomeDir } from './home-paths'
|
|
5
|
+
import { rnxRuntime } from './runtime-delivery'
|
|
6
|
+
|
|
7
|
+
const AUTOMATIC_CLEANUP_GENERATION = 1
|
|
8
|
+
const AUTOMATIC_CLEANUP_COMPLETE_FILE = `automatic-cleanup-v${AUTOMATIC_CLEANUP_GENERATION}.json`
|
|
9
|
+
const AUTOMATIC_CLEANUP_LOCK_FILE = `automatic-cleanup-v${AUTOMATIC_CLEANUP_GENERATION}.lock`
|
|
10
|
+
const AUTOMATIC_CLEANUP_LOCK_STALE_MS = 6 * 60 * 60 * 1000
|
|
11
|
+
// a deferral is recorded so the retry does not happen on literally every
|
|
12
|
+
// command. cleanup defers whenever anything is in use, and on a machine that
|
|
13
|
+
// always has a sim or daemon running that is the permanent state: the complete
|
|
14
|
+
// marker is never written, so every rnx invocation spawned a detached worker
|
|
15
|
+
// that rescanned the home directory, reclaimed nothing, and printed the same
|
|
16
|
+
// "N is in use and will be retried on a later run" line into the caller's
|
|
17
|
+
// stderr. measured on this machine: an identical 662.2 MiB across every
|
|
18
|
+
// invocation over several hours. the retry itself is right, its cadence was
|
|
19
|
+
// not, so it is throttled rather than removed.
|
|
20
|
+
const AUTOMATIC_CLEANUP_DEFERRED_FILE = `automatic-cleanup-v${AUTOMATIC_CLEANUP_GENERATION}.deferred.json`
|
|
21
|
+
const AUTOMATIC_CLEANUP_DEFER_RETRY_MS = 30 * 60 * 1000
|
|
22
|
+
|
|
23
|
+
export type SootsimCleanupKind =
|
|
24
|
+
| 'browser-cache'
|
|
25
|
+
| 'camera-fixture'
|
|
26
|
+
| 'recording'
|
|
27
|
+
| 'runtime-archive'
|
|
28
|
+
| 'runtime-version'
|
|
29
|
+
|
|
30
|
+
export type SootsimCleanupState = 'aggressive' | 'in-use' | 'retained' | 'safe'
|
|
31
|
+
|
|
32
|
+
export interface SootsimCleanupEntry {
|
|
33
|
+
path: string
|
|
34
|
+
bytes: number
|
|
35
|
+
kind: SootsimCleanupKind
|
|
36
|
+
state: SootsimCleanupState
|
|
37
|
+
selected: boolean
|
|
38
|
+
detail: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SootsimCleanupOptions {
|
|
42
|
+
aggressive?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SootsimCleanupPlan {
|
|
46
|
+
home: string
|
|
47
|
+
activeRuntime: string | null
|
|
48
|
+
entries: SootsimCleanupEntry[]
|
|
49
|
+
reclaimableBytes: number
|
|
50
|
+
protectedBytes: number
|
|
51
|
+
totalBytes: number
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface SootsimCleanupResult extends SootsimCleanupPlan {
|
|
55
|
+
removedPaths: string[]
|
|
56
|
+
reclaimedBytes: number
|
|
57
|
+
remainingBytes: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface AutomaticSootsimCleanupResult {
|
|
61
|
+
status: 'already-complete' | 'busy' | 'complete' | 'deferred'
|
|
62
|
+
reclaimedBytes: number
|
|
63
|
+
remainingBytes: number
|
|
64
|
+
deferredInUseBytes: number
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isAutomaticSootsimCleanupComplete(): boolean {
|
|
68
|
+
const completePath = path.join(rnxHomeDir(), AUTOMATIC_CLEANUP_COMPLETE_FILE)
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(fs.readFileSync(completePath, 'utf8')) as {
|
|
71
|
+
generation?: unknown
|
|
72
|
+
}
|
|
73
|
+
return parsed.generation === AUTOMATIC_CLEANUP_GENERATION
|
|
74
|
+
} catch {
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// what the CLI asks before spawning a worker: is there anything for one to do
|
|
80
|
+
// right now. complete means never again; a recent deferral means not yet.
|
|
81
|
+
export function shouldSkipAutomaticSootsimCleanup(now = Date.now()): boolean {
|
|
82
|
+
if (isAutomaticSootsimCleanupComplete()) return true
|
|
83
|
+
const deferredPath = path.join(rnxHomeDir(), AUTOMATIC_CLEANUP_DEFERRED_FILE)
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(fs.readFileSync(deferredPath, 'utf8')) as {
|
|
86
|
+
generation?: unknown
|
|
87
|
+
attemptedAt?: unknown
|
|
88
|
+
}
|
|
89
|
+
if (parsed.generation !== AUTOMATIC_CLEANUP_GENERATION) return false
|
|
90
|
+
if (typeof parsed.attemptedAt !== 'number') return false
|
|
91
|
+
// a clock that moved backwards must not park cleanup forever
|
|
92
|
+
const age = now - parsed.attemptedAt
|
|
93
|
+
return age >= 0 && age < AUTOMATIC_CLEANUP_DEFER_RETRY_MS
|
|
94
|
+
} catch {
|
|
95
|
+
return false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function formatSootsimBytes(bytes: number): string {
|
|
100
|
+
if (bytes < 1024) return `${bytes} B`
|
|
101
|
+
const units = ['KiB', 'MiB', 'GiB', 'TiB']
|
|
102
|
+
let value = bytes / 1024
|
|
103
|
+
let unit = units[0]
|
|
104
|
+
for (let i = 1; i < units.length && value >= 1024; i++) {
|
|
105
|
+
value /= 1024
|
|
106
|
+
unit = units[i]
|
|
107
|
+
}
|
|
108
|
+
return `${value.toFixed(value >= 10 ? 1 : 2)} ${unit}`
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function allocatedBytes(target: string): number {
|
|
112
|
+
let stat: fs.Stats
|
|
113
|
+
try {
|
|
114
|
+
stat = fs.lstatSync(target)
|
|
115
|
+
} catch {
|
|
116
|
+
return 0
|
|
117
|
+
}
|
|
118
|
+
const ownBytes = stat.blocks > 0 ? stat.blocks * 512 : stat.size
|
|
119
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) return ownBytes
|
|
120
|
+
let total = ownBytes
|
|
121
|
+
try {
|
|
122
|
+
for (const child of fs.readdirSync(target)) {
|
|
123
|
+
total += allocatedBytes(path.join(target, child))
|
|
124
|
+
}
|
|
125
|
+
} catch {}
|
|
126
|
+
return total
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function hasBrowserLock(profileRoot: string): boolean {
|
|
130
|
+
for (const name of ['SingletonLock', 'SingletonSocket', 'lockfile']) {
|
|
131
|
+
try {
|
|
132
|
+
if (fs.lstatSync(path.join(profileRoot, name))) return true
|
|
133
|
+
} catch {}
|
|
134
|
+
}
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function collectCacheDirs(root: string, profileRoot: string): SootsimCleanupEntry[] {
|
|
139
|
+
let children: fs.Dirent[]
|
|
140
|
+
try {
|
|
141
|
+
children = fs.readdirSync(root, { withFileTypes: true })
|
|
142
|
+
} catch {
|
|
143
|
+
return []
|
|
144
|
+
}
|
|
145
|
+
const inUse = hasBrowserLock(profileRoot)
|
|
146
|
+
const entries: SootsimCleanupEntry[] = []
|
|
147
|
+
for (const child of children) {
|
|
148
|
+
if (!child.isDirectory() || child.isSymbolicLink()) continue
|
|
149
|
+
const childPath = path.join(root, child.name)
|
|
150
|
+
if (isDisposableBrowserCacheDir(child.name)) {
|
|
151
|
+
entries.push({
|
|
152
|
+
path: childPath,
|
|
153
|
+
bytes: allocatedBytes(childPath),
|
|
154
|
+
kind: 'browser-cache',
|
|
155
|
+
state: inUse ? 'in-use' : 'safe',
|
|
156
|
+
selected: !inUse,
|
|
157
|
+
detail: inUse
|
|
158
|
+
? 'browser profile is open; close it before cleanup'
|
|
159
|
+
: 'disposable browser HTTP, bytecode, or GPU cache',
|
|
160
|
+
})
|
|
161
|
+
continue
|
|
162
|
+
}
|
|
163
|
+
entries.push(...collectCacheDirs(childPath, profileRoot))
|
|
164
|
+
}
|
|
165
|
+
return entries
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function collectPlaywrightCaches(home: string): SootsimCleanupEntry[] {
|
|
169
|
+
const root = path.join(home, 'profiles', 'playwright')
|
|
170
|
+
let profiles: fs.Dirent[]
|
|
171
|
+
try {
|
|
172
|
+
profiles = fs.readdirSync(root, { withFileTypes: true })
|
|
173
|
+
} catch {
|
|
174
|
+
return []
|
|
175
|
+
}
|
|
176
|
+
return profiles.flatMap((profile) => {
|
|
177
|
+
if (!profile.isDirectory() || profile.isSymbolicLink()) return []
|
|
178
|
+
const profileRoot = path.join(root, profile.name)
|
|
179
|
+
return collectCacheDirs(profileRoot, profileRoot)
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function collectElectronCaches(home: string): SootsimCleanupEntry[] {
|
|
184
|
+
const root = path.join(home, 'electron', 'userData')
|
|
185
|
+
return collectCacheDirs(root, root)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function collectRecordingEntries(
|
|
189
|
+
home: string,
|
|
190
|
+
aggressive: boolean,
|
|
191
|
+
): SootsimCleanupEntry[] {
|
|
192
|
+
const root = path.join(home, 'recordings')
|
|
193
|
+
let recordings: fs.Dirent[]
|
|
194
|
+
try {
|
|
195
|
+
recordings = fs.readdirSync(root, { withFileTypes: true })
|
|
196
|
+
} catch {
|
|
197
|
+
return []
|
|
198
|
+
}
|
|
199
|
+
return recordings.map((recording) => {
|
|
200
|
+
const recordingPath = path.join(root, recording.name)
|
|
201
|
+
return {
|
|
202
|
+
path: recordingPath,
|
|
203
|
+
bytes: allocatedBytes(recordingPath),
|
|
204
|
+
kind: 'recording',
|
|
205
|
+
state: 'aggressive',
|
|
206
|
+
selected: aggressive,
|
|
207
|
+
detail: 'user-created recording or captured-frame output',
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// videos staged by `rnx camera play`. a fixture is only a copy of a file
|
|
213
|
+
// the user still has, so it is cheap to reclaim, but the automatic sweep must
|
|
214
|
+
// not pull one out from under a sim that is playing it right now. aggressive
|
|
215
|
+
// only, matching recordings.
|
|
216
|
+
function collectCameraFixtureEntries(
|
|
217
|
+
home: string,
|
|
218
|
+
aggressive: boolean,
|
|
219
|
+
): SootsimCleanupEntry[] {
|
|
220
|
+
const root = path.join(home, 'camera-fixtures')
|
|
221
|
+
let fixtures: fs.Dirent[]
|
|
222
|
+
try {
|
|
223
|
+
fixtures = fs.readdirSync(root, { withFileTypes: true })
|
|
224
|
+
} catch {
|
|
225
|
+
return []
|
|
226
|
+
}
|
|
227
|
+
return fixtures.map((fixture) => {
|
|
228
|
+
const fixturePath = path.join(root, fixture.name)
|
|
229
|
+
return {
|
|
230
|
+
path: fixturePath,
|
|
231
|
+
bytes: allocatedBytes(fixturePath),
|
|
232
|
+
kind: 'camera-fixture',
|
|
233
|
+
state: 'aggressive',
|
|
234
|
+
selected: aggressive,
|
|
235
|
+
detail: 'staged camera fixture video',
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function runtimeEntries(aggressive: boolean): {
|
|
241
|
+
activeRuntime: string | null
|
|
242
|
+
entries: SootsimCleanupEntry[]
|
|
243
|
+
} {
|
|
244
|
+
const inUseVersions = new Set(readLiveRuntimeVersions())
|
|
245
|
+
const protectVersions = [...inUseVersions]
|
|
246
|
+
const normal = rnxRuntime.planLocalRetention({
|
|
247
|
+
retainVersions: 2,
|
|
248
|
+
protectVersions,
|
|
249
|
+
})
|
|
250
|
+
const minimal = rnxRuntime.planLocalRetention({
|
|
251
|
+
retainVersions: 1,
|
|
252
|
+
protectVersions,
|
|
253
|
+
})
|
|
254
|
+
const normallyRemoved = new Set(normal.removeVersions)
|
|
255
|
+
const aggressivelyRemoved = new Set(minimal.removeVersions)
|
|
256
|
+
const allVersions = new Set([
|
|
257
|
+
...normal.keepVersions,
|
|
258
|
+
...normal.removeVersions,
|
|
259
|
+
...minimal.keepVersions,
|
|
260
|
+
...minimal.removeVersions,
|
|
261
|
+
])
|
|
262
|
+
const entries: SootsimCleanupEntry[] = []
|
|
263
|
+
|
|
264
|
+
for (const version of [...allVersions].sort()) {
|
|
265
|
+
const runtimePath = rnxRuntime.product.runtimeDir(version)
|
|
266
|
+
let state: SootsimCleanupState = 'retained'
|
|
267
|
+
let detail = 'retained runtime'
|
|
268
|
+
if (inUseVersions.has(version) && version !== normal.activeVersion) {
|
|
269
|
+
state = 'in-use'
|
|
270
|
+
detail = 'runtime is serving a connected simulator'
|
|
271
|
+
} else if (normallyRemoved.has(version)) {
|
|
272
|
+
state = 'safe'
|
|
273
|
+
detail = 'inactive runtime outside the two-version rollback window'
|
|
274
|
+
} else if (aggressivelyRemoved.has(version)) {
|
|
275
|
+
state = 'aggressive'
|
|
276
|
+
detail = 'most recent rollback runtime'
|
|
277
|
+
} else if (version === normal.activeVersion) {
|
|
278
|
+
detail = 'active runtime; always protected'
|
|
279
|
+
}
|
|
280
|
+
entries.push({
|
|
281
|
+
path: runtimePath,
|
|
282
|
+
bytes: allocatedBytes(runtimePath),
|
|
283
|
+
kind: 'runtime-version',
|
|
284
|
+
state,
|
|
285
|
+
selected: state === 'safe' || (state === 'aggressive' && aggressive),
|
|
286
|
+
detail,
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
for (const cacheFile of normal.removeCacheFiles) {
|
|
291
|
+
entries.push({
|
|
292
|
+
path: cacheFile,
|
|
293
|
+
bytes: allocatedBytes(cacheFile),
|
|
294
|
+
kind: 'runtime-archive',
|
|
295
|
+
state: 'safe',
|
|
296
|
+
selected: true,
|
|
297
|
+
detail: 'downloaded archive is unnecessary after successful extraction',
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
activeRuntime: normal.activeVersion,
|
|
303
|
+
entries,
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function planSootsimCleanup(
|
|
308
|
+
options: SootsimCleanupOptions = {},
|
|
309
|
+
): SootsimCleanupPlan {
|
|
310
|
+
const aggressive = options.aggressive === true
|
|
311
|
+
const home = rnxHomeDir()
|
|
312
|
+
const runtimes = runtimeEntries(aggressive)
|
|
313
|
+
const entries = [
|
|
314
|
+
...runtimes.entries,
|
|
315
|
+
...collectPlaywrightCaches(home),
|
|
316
|
+
...collectElectronCaches(home),
|
|
317
|
+
...collectRecordingEntries(home, aggressive),
|
|
318
|
+
...collectCameraFixtureEntries(home, aggressive),
|
|
319
|
+
].sort((left, right) => right.bytes - left.bytes || left.path.localeCompare(right.path))
|
|
320
|
+
return {
|
|
321
|
+
home,
|
|
322
|
+
activeRuntime: runtimes.activeRuntime,
|
|
323
|
+
entries,
|
|
324
|
+
reclaimableBytes: entries.reduce(
|
|
325
|
+
(total, entry) => total + (entry.selected ? entry.bytes : 0),
|
|
326
|
+
0,
|
|
327
|
+
),
|
|
328
|
+
protectedBytes: entries.reduce(
|
|
329
|
+
(total, entry) => total + (!entry.selected ? entry.bytes : 0),
|
|
330
|
+
0,
|
|
331
|
+
),
|
|
332
|
+
totalBytes: allocatedBytes(home),
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function assertInsideHome(home: string, target: string): void {
|
|
337
|
+
const relative = path.relative(home, target)
|
|
338
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
339
|
+
throw new Error(`refusing cleanup target outside the rnx data home: ${target}`)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function applySootsimCleanup(
|
|
344
|
+
options: SootsimCleanupOptions = {},
|
|
345
|
+
): SootsimCleanupResult {
|
|
346
|
+
const plan = planSootsimCleanup(options)
|
|
347
|
+
const retainVersions = options.aggressive === true ? 1 : 2
|
|
348
|
+
rnxRuntime.pruneLocalRetention({
|
|
349
|
+
retainVersions,
|
|
350
|
+
protectVersions: readLiveRuntimeVersions(),
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
for (const entry of plan.entries) {
|
|
354
|
+
if (!entry.selected) continue
|
|
355
|
+
if (entry.kind === 'runtime-archive' || entry.kind === 'runtime-version') continue
|
|
356
|
+
if (entry.kind === 'browser-cache') {
|
|
357
|
+
const relative = path.relative(plan.home, entry.path)
|
|
358
|
+
const parts = relative.split(path.sep)
|
|
359
|
+
const profileRoot =
|
|
360
|
+
parts[0] === 'profiles' && parts[1] === 'playwright' && parts[2]
|
|
361
|
+
? path.join(plan.home, 'profiles', 'playwright', parts[2])
|
|
362
|
+
: parts[0] === 'electron' && parts[1] === 'userData'
|
|
363
|
+
? path.join(plan.home, 'electron', 'userData')
|
|
364
|
+
: null
|
|
365
|
+
if (profileRoot && hasBrowserLock(profileRoot)) {
|
|
366
|
+
entry.state = 'in-use'
|
|
367
|
+
entry.selected = false
|
|
368
|
+
entry.detail = 'browser profile opened while cleanup was starting'
|
|
369
|
+
continue
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
assertInsideHome(plan.home, entry.path)
|
|
373
|
+
fs.rmSync(entry.path, { recursive: true, force: true })
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const removedPaths = plan.entries
|
|
377
|
+
.filter((entry) => entry.selected && !fs.existsSync(entry.path))
|
|
378
|
+
.map((entry) => entry.path)
|
|
379
|
+
const remainingBytes = allocatedBytes(plan.home)
|
|
380
|
+
return {
|
|
381
|
+
...plan,
|
|
382
|
+
removedPaths,
|
|
383
|
+
reclaimedBytes: Math.max(0, plan.totalBytes - remainingBytes),
|
|
384
|
+
remainingBytes,
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function applyAutomaticSootsimCleanup(): AutomaticSootsimCleanupResult {
|
|
389
|
+
ensureRnxHome()
|
|
390
|
+
const home = rnxHomeDir()
|
|
391
|
+
const completePath = path.join(home, AUTOMATIC_CLEANUP_COMPLETE_FILE)
|
|
392
|
+
const deferredPath = path.join(home, AUTOMATIC_CLEANUP_DEFERRED_FILE)
|
|
393
|
+
const lockPath = path.join(home, AUTOMATIC_CLEANUP_LOCK_FILE)
|
|
394
|
+
|
|
395
|
+
if (isAutomaticSootsimCleanupComplete()) {
|
|
396
|
+
return {
|
|
397
|
+
status: 'already-complete',
|
|
398
|
+
reclaimedBytes: 0,
|
|
399
|
+
remainingBytes: 0,
|
|
400
|
+
deferredInUseBytes: 0,
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
let lockFd: number | null = null
|
|
405
|
+
for (let attempt = 0; attempt < 2 && lockFd === null; attempt++) {
|
|
406
|
+
try {
|
|
407
|
+
const candidateFd = fs.openSync(lockPath, 'wx')
|
|
408
|
+
try {
|
|
409
|
+
fs.writeFileSync(
|
|
410
|
+
candidateFd,
|
|
411
|
+
`${JSON.stringify({ pid: process.pid, startedAt: Date.now() })}\n`,
|
|
412
|
+
'utf8',
|
|
413
|
+
)
|
|
414
|
+
lockFd = candidateFd
|
|
415
|
+
} catch (error) {
|
|
416
|
+
fs.closeSync(candidateFd)
|
|
417
|
+
try {
|
|
418
|
+
fs.unlinkSync(lockPath)
|
|
419
|
+
} catch {}
|
|
420
|
+
throw error
|
|
421
|
+
}
|
|
422
|
+
} catch (error) {
|
|
423
|
+
const code = error instanceof Error && 'code' in error ? error.code : null
|
|
424
|
+
if (code !== 'EEXIST') throw error
|
|
425
|
+
|
|
426
|
+
let ownerPid = 0
|
|
427
|
+
let ownerStartedAt = 0
|
|
428
|
+
let originalStat: fs.Stats | null = null
|
|
429
|
+
try {
|
|
430
|
+
originalStat = fs.lstatSync(lockPath)
|
|
431
|
+
const owner = JSON.parse(fs.readFileSync(lockPath, 'utf8')) as {
|
|
432
|
+
pid?: unknown
|
|
433
|
+
startedAt?: unknown
|
|
434
|
+
}
|
|
435
|
+
ownerPid = typeof owner.pid === 'number' ? owner.pid : 0
|
|
436
|
+
ownerStartedAt = typeof owner.startedAt === 'number' ? owner.startedAt : 0
|
|
437
|
+
} catch {}
|
|
438
|
+
if (
|
|
439
|
+
ownerPid > 0 &&
|
|
440
|
+
ownerStartedAt > 0 &&
|
|
441
|
+
Date.now() - ownerStartedAt < AUTOMATIC_CLEANUP_LOCK_STALE_MS
|
|
442
|
+
) {
|
|
443
|
+
try {
|
|
444
|
+
process.kill(ownerPid, 0)
|
|
445
|
+
return {
|
|
446
|
+
status: 'busy',
|
|
447
|
+
reclaimedBytes: 0,
|
|
448
|
+
remainingBytes: 0,
|
|
449
|
+
deferredInUseBytes: 0,
|
|
450
|
+
}
|
|
451
|
+
} catch {}
|
|
452
|
+
}
|
|
453
|
+
if (!originalStat) continue
|
|
454
|
+
try {
|
|
455
|
+
const currentStat = fs.lstatSync(lockPath)
|
|
456
|
+
if (
|
|
457
|
+
currentStat.dev !== originalStat.dev ||
|
|
458
|
+
currentStat.ino !== originalStat.ino
|
|
459
|
+
) {
|
|
460
|
+
return {
|
|
461
|
+
status: 'busy',
|
|
462
|
+
reclaimedBytes: 0,
|
|
463
|
+
remainingBytes: 0,
|
|
464
|
+
deferredInUseBytes: 0,
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
fs.unlinkSync(lockPath)
|
|
468
|
+
} catch {}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (lockFd === null) {
|
|
473
|
+
return {
|
|
474
|
+
status: 'busy',
|
|
475
|
+
reclaimedBytes: 0,
|
|
476
|
+
remainingBytes: 0,
|
|
477
|
+
deferredInUseBytes: 0,
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
try {
|
|
482
|
+
if (isAutomaticSootsimCleanupComplete()) {
|
|
483
|
+
return {
|
|
484
|
+
status: 'already-complete',
|
|
485
|
+
reclaimedBytes: 0,
|
|
486
|
+
remainingBytes: 0,
|
|
487
|
+
deferredInUseBytes: 0,
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const result = applySootsimCleanup()
|
|
492
|
+
const deferredInUseBytes = result.entries.reduce(
|
|
493
|
+
(total, entry) => total + (entry.state === 'in-use' ? entry.bytes : 0),
|
|
494
|
+
0,
|
|
495
|
+
)
|
|
496
|
+
if (deferredInUseBytes > 0) {
|
|
497
|
+
const deferredTmp = `${deferredPath}.${process.pid}.tmp`
|
|
498
|
+
fs.writeFileSync(
|
|
499
|
+
deferredTmp,
|
|
500
|
+
`${JSON.stringify({
|
|
501
|
+
generation: AUTOMATIC_CLEANUP_GENERATION,
|
|
502
|
+
attemptedAt: Date.now(),
|
|
503
|
+
deferredInUseBytes,
|
|
504
|
+
})}\n`,
|
|
505
|
+
'utf8',
|
|
506
|
+
)
|
|
507
|
+
fs.renameSync(deferredTmp, deferredPath)
|
|
508
|
+
return {
|
|
509
|
+
status: 'deferred',
|
|
510
|
+
reclaimedBytes: result.reclaimedBytes,
|
|
511
|
+
remainingBytes: result.remainingBytes,
|
|
512
|
+
deferredInUseBytes,
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const tmp = `${completePath}.${process.pid}.tmp`
|
|
517
|
+
fs.writeFileSync(
|
|
518
|
+
tmp,
|
|
519
|
+
`${JSON.stringify({
|
|
520
|
+
generation: AUTOMATIC_CLEANUP_GENERATION,
|
|
521
|
+
completedAt: Date.now(),
|
|
522
|
+
reclaimedBytes: result.reclaimedBytes,
|
|
523
|
+
})}\n`,
|
|
524
|
+
'utf8',
|
|
525
|
+
)
|
|
526
|
+
fs.renameSync(tmp, completePath)
|
|
527
|
+
fs.rmSync(deferredPath, { force: true })
|
|
528
|
+
return {
|
|
529
|
+
status: 'complete',
|
|
530
|
+
reclaimedBytes: result.reclaimedBytes,
|
|
531
|
+
remainingBytes: result.remainingBytes,
|
|
532
|
+
deferredInUseBytes: 0,
|
|
533
|
+
}
|
|
534
|
+
} finally {
|
|
535
|
+
fs.closeSync(lockFd)
|
|
536
|
+
try {
|
|
537
|
+
fs.unlinkSync(lockPath)
|
|
538
|
+
} catch {}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// hosted-runner previews reach first-render much slower than local dev: the
|
|
2
|
+
// guest bundle (often tens of MB) re-evals and re-hydrates on every launchApp
|
|
3
|
+
// hard reload, and it is served over a cloudflared tunnel. run.sh sets
|
|
4
|
+
// SOOTSIM_FLOW_TIMEOUT_SCALE for the recording lane so a slow-but-working app
|
|
5
|
+
// clears its waits without per-app timeouts. off (1x) locally, clamped to 1..10.
|
|
6
|
+
//
|
|
7
|
+
// every deadline a hosted launchApp has to clear must use this, or the lane is
|
|
8
|
+
// only half-scaled: the flow runner's own waits scaled while the CLI's
|
|
9
|
+
// post-navigation readiness poll kept a flat ceiling, so a launchApp that was
|
|
10
|
+
// working but slow failed the flow.
|
|
11
|
+
export const FLOW_TIMEOUT_SCALE_ENV = 'SOOTSIM_FLOW_TIMEOUT_SCALE'
|
|
12
|
+
|
|
13
|
+
export function flowTimeoutScale(): number {
|
|
14
|
+
return Math.min(10, Math.max(1, Number(process.env[FLOW_TIMEOUT_SCALE_ENV]) || 1))
|
|
15
|
+
}
|