rnxsim 0.0.0 → 0.1.313
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 +21 -0
- package/README.md +512 -0
- package/detox/colors.ts +54 -0
- package/detox/config-loader.ts +135 -0
- package/detox/element-types.ts +39 -0
- package/detox/expectations.ts +537 -0
- package/detox/gestures.ts +480 -0
- package/detox/index.ts +2118 -0
- package/detox/jest-preset.cjs +51 -0
- package/detox/jest-setup-after-env.cjs +18 -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.ts +43 -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-cli/bin.js +15 -0
- package/dist-cli/chunks/agent-XZ2KTPCU.js +48 -0
- package/dist-cli/chunks/agent-wrapper-JJYYW2WH.js +17 -0
- package/dist-cli/chunks/app-fonts-IXRNQG6B.js +4 -0
- package/dist-cli/chunks/assert-54T5SK5F.js +49 -0
- package/dist-cli/chunks/auth-FI5UDI45.js +12 -0
- package/dist-cli/chunks/beta-JV6UKADW.js +4 -0
- package/dist-cli/chunks/camera-VL73YIKP.js +22 -0
- package/dist-cli/chunks/chunk-2D2UPBBR.js +4 -0
- package/dist-cli/chunks/chunk-2YR5BGA5.js +5 -0
- package/dist-cli/chunks/chunk-3NV2NCNX.js +4 -0
- package/dist-cli/chunks/chunk-46EUUFJ5.js +4 -0
- package/dist-cli/chunks/chunk-4NPPOV2N.js +5 -0
- package/dist-cli/chunks/chunk-5DHC6KHQ.js +7 -0
- package/dist-cli/chunks/chunk-5TEF3ET3.js +74 -0
- package/dist-cli/chunks/chunk-5TPRP5QT.js +4 -0
- package/dist-cli/chunks/chunk-5YJCOWCH.js +4 -0
- package/dist-cli/chunks/chunk-7GN3LVWB.js +4 -0
- package/dist-cli/chunks/chunk-BBULZ7CG.js +568 -0
- package/dist-cli/chunks/chunk-BTWORNNG.js +71 -0
- package/dist-cli/chunks/chunk-D4FFVGI5.js +5 -0
- package/dist-cli/chunks/chunk-DCEMHR2Y.js +4 -0
- package/dist-cli/chunks/chunk-DZS6WPUI.js +4 -0
- package/dist-cli/chunks/chunk-F5ZRSS3C.js +3 -0
- package/dist-cli/chunks/chunk-FSUYIVJ6.js +9 -0
- package/dist-cli/chunks/chunk-G2WW6L2C.js +23 -0
- package/dist-cli/chunks/chunk-GASE6UBA.js +4 -0
- package/dist-cli/chunks/chunk-GGRX24GF.js +142 -0
- package/dist-cli/chunks/chunk-HI5TFJWN.js +5 -0
- package/dist-cli/chunks/chunk-IJ5CAZZC.js +5 -0
- package/dist-cli/chunks/chunk-IJO63TDP.js +4 -0
- package/dist-cli/chunks/chunk-KTHV3RUS.js +26 -0
- package/dist-cli/chunks/chunk-LF2ZVT7O.js +6 -0
- package/dist-cli/chunks/chunk-MJRLLB4R.js +38 -0
- package/dist-cli/chunks/chunk-NFK7T35W.js +4 -0
- package/dist-cli/chunks/chunk-NMF2ZMZQ.js +15 -0
- package/dist-cli/chunks/chunk-OHAZNXLK.js +4 -0
- package/dist-cli/chunks/chunk-OVFJFXUD.js +101 -0
- package/dist-cli/chunks/chunk-OZSSI4WN.js +28 -0
- package/dist-cli/chunks/chunk-QKDWYITG.js +25 -0
- package/dist-cli/chunks/chunk-RSZWCKNT.js +30 -0
- package/dist-cli/chunks/chunk-RTN5C5RL.js +4 -0
- package/dist-cli/chunks/chunk-TIVZIMMW.js +4 -0
- package/dist-cli/chunks/chunk-TZFFR3SD.js +7 -0
- package/dist-cli/chunks/chunk-UC6U3MML.js +5 -0
- package/dist-cli/chunks/chunk-VFCMSYZK.js +6 -0
- package/dist-cli/chunks/chunk-VNQEB4L7.js +5 -0
- package/dist-cli/chunks/chunk-VZXWHRUZ.js +983 -0
- package/dist-cli/chunks/chunk-W6K4EFPH.js +107 -0
- package/dist-cli/chunks/chunk-WEXDAC74.js +4 -0
- package/dist-cli/chunks/chunk-WF3T4SVI.js +14 -0
- package/dist-cli/chunks/chunk-WINYQ44O.js +4 -0
- package/dist-cli/chunks/chunk-WMIIKMGK.js +10 -0
- package/dist-cli/chunks/chunk-WUSWBCWA.js +507 -0
- package/dist-cli/chunks/chunk-WWZIXIRD.js +4 -0
- package/dist-cli/chunks/chunk-XEVZYVIW.js +766 -0
- package/dist-cli/chunks/chunk-YDGQTMQL.js +4 -0
- package/dist-cli/chunks/chunk-YIFT42WN.js +4 -0
- package/dist-cli/chunks/chunk-ZMJD5GEC.js +125 -0
- package/dist-cli/chunks/cleanup-P27PA6JI.js +25 -0
- package/dist-cli/chunks/cli-version-WWLPBDQ7.js +4 -0
- package/dist-cli/chunks/compat-ZD65FED3.js +20 -0
- package/dist-cli/chunks/config-XMJRNM2A.js +13 -0
- package/dist-cli/chunks/control-KMIQT3QP.js +4 -0
- package/dist-cli/chunks/daemon-G2ME7NLB.js +4 -0
- package/dist-cli/chunks/debug-PT4HOP7N.js +200 -0
- package/dist-cli/chunks/desktop-S3FG72AK.js +31 -0
- package/dist-cli/chunks/detox-B3D4IFCN.js +52 -0
- package/dist-cli/chunks/device-XBNDSB2R.js +18 -0
- package/dist-cli/chunks/diagnose-HMQXJE5N.js +43 -0
- package/dist-cli/chunks/disk-cleanup-BLCZ5BSZ.js +4 -0
- package/dist-cli/chunks/drivers-LDECZGP2.js +4 -0
- package/dist-cli/chunks/film-BJGTBYZB.js +74 -0
- package/dist-cli/chunks/flow-UEQNVTU7.js +4 -0
- package/dist-cli/chunks/help-T5FYSVGB.js +4 -0
- package/dist-cli/chunks/hidden-runtime-alias-ANOYADHM.js +4 -0
- package/dist-cli/chunks/home-paths-GT3LFNOR.js +4 -0
- package/dist-cli/chunks/inspect-ZA6XF5LD.js +4 -0
- package/dist-cli/chunks/install-desktop-TIMUDHPL.js +4 -0
- package/dist-cli/chunks/login-FJ737MWG.js +28 -0
- package/dist-cli/chunks/logout-ZCNMMHMY.js +4 -0
- package/dist-cli/chunks/maestro-SZTNKLDF.js +104 -0
- package/dist-cli/chunks/maestro-generate-DCFAIZ4H.js +36 -0
- package/dist-cli/chunks/mode-GRMQCRXR.js +26 -0
- package/dist-cli/chunks/optional-demo-registry-W36EWFFB.js +4 -0
- package/dist-cli/chunks/perf-QYBAAUZG.js +48 -0
- package/dist-cli/chunks/permissions-3QCQ6VF4.js +13 -0
- package/dist-cli/chunks/record-QPPC2S4E.js +72 -0
- package/dist-cli/chunks/report-issue-7NMFP4HK.js +29 -0
- package/dist-cli/chunks/runtime-XOAXMSTU.js +4 -0
- package/dist-cli/chunks/screenshot-command-67AECJFB.js +159 -0
- package/dist-cli/chunks/screenshot-layers-ASWBYPJL.js +20 -0
- package/dist-cli/chunks/screenshots-capture-PXA3HFQK.js +42 -0
- package/dist-cli/chunks/serve-BI2NBAXG.js +44 -0
- package/dist-cli/chunks/setup-7DWPMRSB.js +32 -0
- package/dist-cli/chunks/skills-S3Y22TUA.js +50 -0
- package/dist-cli/chunks/storage-XUIMJWAJ.js +37 -0
- package/dist-cli/chunks/store-JTHEJLAZ.js +4 -0
- package/dist-cli/chunks/telemetry-ZYJGD2DB.js +4 -0
- package/dist-cli/chunks/timeline-TMPLQPSP.js +24 -0
- package/dist-cli/chunks/upgrade-7HDSIM7K.js +7 -0
- package/dist-cli/chunks/upload-GMSZPWM6.js +4 -0
- package/dist-cli/chunks/version-HOCHZ37L.js +6 -0
- package/dist-cli/chunks/web-DG3WBYD3.js +4 -0
- package/dist-cli/chunks/what-happened-XFVUTZR7.js +25 -0
- package/dist-lib/agent-daemon-client.cjs +468 -0
- package/dist-lib/agent-events.cjs +48 -0
- package/dist-lib/agent-identity.cjs +53 -0
- package/dist-lib/agent-sessions.cjs +725 -0
- package/dist-lib/attached-projects.cjs +526 -0
- package/dist-lib/auth/shared-session.cjs +195 -0
- package/dist-lib/backend-origin.cjs +70 -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/cli-constants.cjs +32 -0
- package/dist-lib/config.cjs +96 -0
- package/dist-lib/detox/index.cjs +2863 -0
- package/dist-lib/detox/jest-preset.cjs +51 -0
- package/dist-lib/dev-bundle-resolution.cjs +223 -0
- package/dist-lib/home-paths.cjs +421 -0
- package/dist-lib/host/bridge-host.cjs +5600 -0
- package/dist-lib/host/fetch-proxy-handler.cjs +373 -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/websocket-proxy.cjs +214 -0
- package/dist-lib/index.cjs +513 -0
- package/dist-lib/jump-to-source-babel.cjs +93 -0
- package/dist-lib/menu.cjs +620 -0
- package/dist-lib/menu.mjs +581 -0
- package/dist-lib/metro.cjs +297 -0
- package/dist-lib/profiles.cjs +246 -0
- package/dist-lib/public-brand.cjs +39 -0
- package/dist-lib/render-mode.cjs +54 -0
- package/dist-lib/scripts/dev-server-scanner.cjs +1034 -0
- package/dist-lib/sdk.cjs +2333 -0
- package/dist-lib/sdk.mjs +2240 -0
- package/dist-lib/skills.cjs +23922 -0
- package/dist-lib/vite.cjs +358 -0
- package/package.json +189 -18
- package/scripts/dev-server-scanner.ts +941 -0
- package/scripts/optional-demo-registry.ts +113 -0
- package/scripts/postinstall.cjs +3 -0
- package/scripts/preuninstall.cjs +118 -0
- package/skills/contrast/SKILL.md +171 -0
- package/skills/rnx-debug/SKILL.md +425 -0
- package/skills/rnx-perf/SKILL.md +295 -0
- package/skills/rnx-setup/SKILL.md +160 -0
- package/skills/rnx-test/SKILL.md +292 -0
- package/skills/rnx-visual/SKILL.md +122 -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 +573 -0
- package/src/attached-projects.ts +539 -0
- package/src/auth/shared-session.ts +204 -0
- package/src/backend-origin.ts +49 -0
- package/src/beta.ts +19 -0
- package/src/bridge-constants.ts +171 -0
- package/src/browser-cache.ts +16 -0
- package/src/cli-constants.ts +1 -0
- package/src/cli-version.ts +32 -0
- package/src/codex-client.ts +215 -0
- package/src/config.ts +134 -0
- package/src/dev-bundle-resolution.ts +236 -0
- package/src/disk-cleanup.ts +466 -0
- package/src/home-paths.ts +552 -0
- package/src/host/agent-host.ts +666 -0
- package/src/host/bridge-host.ts +2626 -0
- package/src/host/fetch-proxy-handler.ts +397 -0
- package/src/host/fetch-proxy-overrides.ts +51 -0
- package/src/host/open-url.ts +89 -0
- package/src/host/websocket-proxy.ts +210 -0
- package/src/index.ts +12 -705
- package/src/jump-to-source-babel.ts +135 -0
- package/src/menu.ts +734 -0
- package/src/metro-plugin.ts +139 -0
- package/src/native-dev-bundle-url.ts +96 -0
- package/src/native-seam-manifest.ts +341 -0
- package/src/plist.ts +27 -0
- package/src/poll-until.ts +92 -0
- package/src/profiles.ts +179 -0
- package/src/public-brand.ts +9 -0
- package/src/render-mode.ts +27 -0
- package/src/runtime-assets.ts +228 -0
- package/src/runtime-delivery.ts +54 -0
- package/src/screenshots/compose.ts +422 -0
- package/src/screenshots/frame-compose.ts +454 -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 +7 -0
- package/src/settings-bundle.ts +418 -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/team-machine-prompt.ts +73 -0
- package/src/vite-plugin-one.ts +190 -0
- package/src/vite-plugin.ts +1750 -0
- package/src/worklets-babel.ts +134 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { isDisposableBrowserCacheDir } from './browser-cache'
|
|
4
|
+
import { ensureSootsimHome, readLiveRuntimeVersions, sootsimHomeDir } from './home-paths'
|
|
5
|
+
import { sootsimRuntime } 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
|
+
|
|
12
|
+
export type SootsimCleanupKind =
|
|
13
|
+
| 'browser-cache'
|
|
14
|
+
| 'recording'
|
|
15
|
+
| 'runtime-archive'
|
|
16
|
+
| 'runtime-version'
|
|
17
|
+
|
|
18
|
+
export type SootsimCleanupState = 'aggressive' | 'in-use' | 'retained' | 'safe'
|
|
19
|
+
|
|
20
|
+
export interface SootsimCleanupEntry {
|
|
21
|
+
path: string
|
|
22
|
+
bytes: number
|
|
23
|
+
kind: SootsimCleanupKind
|
|
24
|
+
state: SootsimCleanupState
|
|
25
|
+
selected: boolean
|
|
26
|
+
detail: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SootsimCleanupOptions {
|
|
30
|
+
aggressive?: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SootsimCleanupPlan {
|
|
34
|
+
home: string
|
|
35
|
+
activeRuntime: string | null
|
|
36
|
+
entries: SootsimCleanupEntry[]
|
|
37
|
+
reclaimableBytes: number
|
|
38
|
+
protectedBytes: number
|
|
39
|
+
totalBytes: number
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SootsimCleanupResult extends SootsimCleanupPlan {
|
|
43
|
+
removedPaths: string[]
|
|
44
|
+
reclaimedBytes: number
|
|
45
|
+
remainingBytes: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface AutomaticSootsimCleanupResult {
|
|
49
|
+
status: 'already-complete' | 'busy' | 'complete' | 'deferred'
|
|
50
|
+
reclaimedBytes: number
|
|
51
|
+
remainingBytes: number
|
|
52
|
+
deferredInUseBytes: number
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function isAutomaticSootsimCleanupComplete(): boolean {
|
|
56
|
+
const completePath = path.join(sootsimHomeDir(), AUTOMATIC_CLEANUP_COMPLETE_FILE)
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(fs.readFileSync(completePath, 'utf8')) as {
|
|
59
|
+
generation?: unknown
|
|
60
|
+
}
|
|
61
|
+
return parsed.generation === AUTOMATIC_CLEANUP_GENERATION
|
|
62
|
+
} catch {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function formatSootsimBytes(bytes: number): string {
|
|
68
|
+
if (bytes < 1024) return `${bytes} B`
|
|
69
|
+
const units = ['KiB', 'MiB', 'GiB', 'TiB']
|
|
70
|
+
let value = bytes / 1024
|
|
71
|
+
let unit = units[0]
|
|
72
|
+
for (let i = 1; i < units.length && value >= 1024; i++) {
|
|
73
|
+
value /= 1024
|
|
74
|
+
unit = units[i]
|
|
75
|
+
}
|
|
76
|
+
return `${value.toFixed(value >= 10 ? 1 : 2)} ${unit}`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function allocatedBytes(target: string): number {
|
|
80
|
+
let stat: fs.Stats
|
|
81
|
+
try {
|
|
82
|
+
stat = fs.lstatSync(target)
|
|
83
|
+
} catch {
|
|
84
|
+
return 0
|
|
85
|
+
}
|
|
86
|
+
const ownBytes = stat.blocks > 0 ? stat.blocks * 512 : stat.size
|
|
87
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) return ownBytes
|
|
88
|
+
let total = ownBytes
|
|
89
|
+
try {
|
|
90
|
+
for (const child of fs.readdirSync(target)) {
|
|
91
|
+
total += allocatedBytes(path.join(target, child))
|
|
92
|
+
}
|
|
93
|
+
} catch {}
|
|
94
|
+
return total
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function hasBrowserLock(profileRoot: string): boolean {
|
|
98
|
+
for (const name of ['SingletonLock', 'SingletonSocket', 'lockfile']) {
|
|
99
|
+
try {
|
|
100
|
+
if (fs.lstatSync(path.join(profileRoot, name))) return true
|
|
101
|
+
} catch {}
|
|
102
|
+
}
|
|
103
|
+
return false
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function collectCacheDirs(root: string, profileRoot: string): SootsimCleanupEntry[] {
|
|
107
|
+
let children: fs.Dirent[]
|
|
108
|
+
try {
|
|
109
|
+
children = fs.readdirSync(root, { withFileTypes: true })
|
|
110
|
+
} catch {
|
|
111
|
+
return []
|
|
112
|
+
}
|
|
113
|
+
const inUse = hasBrowserLock(profileRoot)
|
|
114
|
+
const entries: SootsimCleanupEntry[] = []
|
|
115
|
+
for (const child of children) {
|
|
116
|
+
if (!child.isDirectory() || child.isSymbolicLink()) continue
|
|
117
|
+
const childPath = path.join(root, child.name)
|
|
118
|
+
if (isDisposableBrowserCacheDir(child.name)) {
|
|
119
|
+
entries.push({
|
|
120
|
+
path: childPath,
|
|
121
|
+
bytes: allocatedBytes(childPath),
|
|
122
|
+
kind: 'browser-cache',
|
|
123
|
+
state: inUse ? 'in-use' : 'safe',
|
|
124
|
+
selected: !inUse,
|
|
125
|
+
detail: inUse
|
|
126
|
+
? 'browser profile is open; close it before cleanup'
|
|
127
|
+
: 'disposable browser HTTP, bytecode, or GPU cache',
|
|
128
|
+
})
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
entries.push(...collectCacheDirs(childPath, profileRoot))
|
|
132
|
+
}
|
|
133
|
+
return entries
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function collectPlaywrightCaches(home: string): SootsimCleanupEntry[] {
|
|
137
|
+
const root = path.join(home, 'profiles', 'playwright')
|
|
138
|
+
let profiles: fs.Dirent[]
|
|
139
|
+
try {
|
|
140
|
+
profiles = fs.readdirSync(root, { withFileTypes: true })
|
|
141
|
+
} catch {
|
|
142
|
+
return []
|
|
143
|
+
}
|
|
144
|
+
return profiles.flatMap((profile) => {
|
|
145
|
+
if (!profile.isDirectory() || profile.isSymbolicLink()) return []
|
|
146
|
+
const profileRoot = path.join(root, profile.name)
|
|
147
|
+
return collectCacheDirs(profileRoot, profileRoot)
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function collectElectronCaches(home: string): SootsimCleanupEntry[] {
|
|
152
|
+
const root = path.join(home, 'electron', 'userData')
|
|
153
|
+
return collectCacheDirs(root, root)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function collectRecordingEntries(
|
|
157
|
+
home: string,
|
|
158
|
+
aggressive: boolean,
|
|
159
|
+
): SootsimCleanupEntry[] {
|
|
160
|
+
const root = path.join(home, 'recordings')
|
|
161
|
+
let recordings: fs.Dirent[]
|
|
162
|
+
try {
|
|
163
|
+
recordings = fs.readdirSync(root, { withFileTypes: true })
|
|
164
|
+
} catch {
|
|
165
|
+
return []
|
|
166
|
+
}
|
|
167
|
+
return recordings.map((recording) => {
|
|
168
|
+
const recordingPath = path.join(root, recording.name)
|
|
169
|
+
return {
|
|
170
|
+
path: recordingPath,
|
|
171
|
+
bytes: allocatedBytes(recordingPath),
|
|
172
|
+
kind: 'recording',
|
|
173
|
+
state: 'aggressive',
|
|
174
|
+
selected: aggressive,
|
|
175
|
+
detail: 'user-created recording or captured-frame output',
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function runtimeEntries(aggressive: boolean): {
|
|
181
|
+
activeRuntime: string | null
|
|
182
|
+
entries: SootsimCleanupEntry[]
|
|
183
|
+
} {
|
|
184
|
+
const inUseVersions = new Set(readLiveRuntimeVersions())
|
|
185
|
+
const protectVersions = [...inUseVersions]
|
|
186
|
+
const normal = sootsimRuntime.planLocalRetention({
|
|
187
|
+
retainVersions: 2,
|
|
188
|
+
protectVersions,
|
|
189
|
+
})
|
|
190
|
+
const minimal = sootsimRuntime.planLocalRetention({
|
|
191
|
+
retainVersions: 1,
|
|
192
|
+
protectVersions,
|
|
193
|
+
})
|
|
194
|
+
const normallyRemoved = new Set(normal.removeVersions)
|
|
195
|
+
const aggressivelyRemoved = new Set(minimal.removeVersions)
|
|
196
|
+
const allVersions = new Set([
|
|
197
|
+
...normal.keepVersions,
|
|
198
|
+
...normal.removeVersions,
|
|
199
|
+
...minimal.keepVersions,
|
|
200
|
+
...minimal.removeVersions,
|
|
201
|
+
])
|
|
202
|
+
const entries: SootsimCleanupEntry[] = []
|
|
203
|
+
|
|
204
|
+
for (const version of [...allVersions].sort()) {
|
|
205
|
+
const runtimePath = sootsimRuntime.product.runtimeDir(version)
|
|
206
|
+
let state: SootsimCleanupState = 'retained'
|
|
207
|
+
let detail = 'retained runtime'
|
|
208
|
+
if (inUseVersions.has(version) && version !== normal.activeVersion) {
|
|
209
|
+
state = 'in-use'
|
|
210
|
+
detail = 'runtime is serving a connected simulator'
|
|
211
|
+
} else if (normallyRemoved.has(version)) {
|
|
212
|
+
state = 'safe'
|
|
213
|
+
detail = 'inactive runtime outside the two-version rollback window'
|
|
214
|
+
} else if (aggressivelyRemoved.has(version)) {
|
|
215
|
+
state = 'aggressive'
|
|
216
|
+
detail = 'most recent rollback runtime'
|
|
217
|
+
} else if (version === normal.activeVersion) {
|
|
218
|
+
detail = 'active runtime; always protected'
|
|
219
|
+
}
|
|
220
|
+
entries.push({
|
|
221
|
+
path: runtimePath,
|
|
222
|
+
bytes: allocatedBytes(runtimePath),
|
|
223
|
+
kind: 'runtime-version',
|
|
224
|
+
state,
|
|
225
|
+
selected: state === 'safe' || (state === 'aggressive' && aggressive),
|
|
226
|
+
detail,
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
for (const cacheFile of normal.removeCacheFiles) {
|
|
231
|
+
entries.push({
|
|
232
|
+
path: cacheFile,
|
|
233
|
+
bytes: allocatedBytes(cacheFile),
|
|
234
|
+
kind: 'runtime-archive',
|
|
235
|
+
state: 'safe',
|
|
236
|
+
selected: true,
|
|
237
|
+
detail: 'downloaded archive is unnecessary after successful extraction',
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
activeRuntime: normal.activeVersion,
|
|
243
|
+
entries,
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function planSootsimCleanup(
|
|
248
|
+
options: SootsimCleanupOptions = {},
|
|
249
|
+
): SootsimCleanupPlan {
|
|
250
|
+
const aggressive = options.aggressive === true
|
|
251
|
+
const home = sootsimHomeDir()
|
|
252
|
+
const runtimes = runtimeEntries(aggressive)
|
|
253
|
+
const entries = [
|
|
254
|
+
...runtimes.entries,
|
|
255
|
+
...collectPlaywrightCaches(home),
|
|
256
|
+
...collectElectronCaches(home),
|
|
257
|
+
...collectRecordingEntries(home, aggressive),
|
|
258
|
+
].sort((left, right) => right.bytes - left.bytes || left.path.localeCompare(right.path))
|
|
259
|
+
return {
|
|
260
|
+
home,
|
|
261
|
+
activeRuntime: runtimes.activeRuntime,
|
|
262
|
+
entries,
|
|
263
|
+
reclaimableBytes: entries.reduce(
|
|
264
|
+
(total, entry) => total + (entry.selected ? entry.bytes : 0),
|
|
265
|
+
0,
|
|
266
|
+
),
|
|
267
|
+
protectedBytes: entries.reduce(
|
|
268
|
+
(total, entry) => total + (!entry.selected ? entry.bytes : 0),
|
|
269
|
+
0,
|
|
270
|
+
),
|
|
271
|
+
totalBytes: allocatedBytes(home),
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function assertInsideHome(home: string, target: string): void {
|
|
276
|
+
const relative = path.relative(home, target)
|
|
277
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
278
|
+
throw new Error(`refusing cleanup target outside the rnx data home: ${target}`)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function applySootsimCleanup(
|
|
283
|
+
options: SootsimCleanupOptions = {},
|
|
284
|
+
): SootsimCleanupResult {
|
|
285
|
+
const plan = planSootsimCleanup(options)
|
|
286
|
+
const retainVersions = options.aggressive === true ? 1 : 2
|
|
287
|
+
sootsimRuntime.pruneLocalRetention({
|
|
288
|
+
retainVersions,
|
|
289
|
+
protectVersions: readLiveRuntimeVersions(),
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
for (const entry of plan.entries) {
|
|
293
|
+
if (!entry.selected) continue
|
|
294
|
+
if (entry.kind === 'runtime-archive' || entry.kind === 'runtime-version') continue
|
|
295
|
+
if (entry.kind === 'browser-cache') {
|
|
296
|
+
const relative = path.relative(plan.home, entry.path)
|
|
297
|
+
const parts = relative.split(path.sep)
|
|
298
|
+
const profileRoot =
|
|
299
|
+
parts[0] === 'profiles' && parts[1] === 'playwright' && parts[2]
|
|
300
|
+
? path.join(plan.home, 'profiles', 'playwright', parts[2])
|
|
301
|
+
: parts[0] === 'electron' && parts[1] === 'userData'
|
|
302
|
+
? path.join(plan.home, 'electron', 'userData')
|
|
303
|
+
: null
|
|
304
|
+
if (profileRoot && hasBrowserLock(profileRoot)) {
|
|
305
|
+
entry.state = 'in-use'
|
|
306
|
+
entry.selected = false
|
|
307
|
+
entry.detail = 'browser profile opened while cleanup was starting'
|
|
308
|
+
continue
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
assertInsideHome(plan.home, entry.path)
|
|
312
|
+
fs.rmSync(entry.path, { recursive: true, force: true })
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const removedPaths = plan.entries
|
|
316
|
+
.filter((entry) => entry.selected && !fs.existsSync(entry.path))
|
|
317
|
+
.map((entry) => entry.path)
|
|
318
|
+
const remainingBytes = allocatedBytes(plan.home)
|
|
319
|
+
return {
|
|
320
|
+
...plan,
|
|
321
|
+
removedPaths,
|
|
322
|
+
reclaimedBytes: Math.max(0, plan.totalBytes - remainingBytes),
|
|
323
|
+
remainingBytes,
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function applyAutomaticSootsimCleanup(): AutomaticSootsimCleanupResult {
|
|
328
|
+
ensureSootsimHome()
|
|
329
|
+
const home = sootsimHomeDir()
|
|
330
|
+
const completePath = path.join(home, AUTOMATIC_CLEANUP_COMPLETE_FILE)
|
|
331
|
+
const lockPath = path.join(home, AUTOMATIC_CLEANUP_LOCK_FILE)
|
|
332
|
+
|
|
333
|
+
if (isAutomaticSootsimCleanupComplete()) {
|
|
334
|
+
return {
|
|
335
|
+
status: 'already-complete',
|
|
336
|
+
reclaimedBytes: 0,
|
|
337
|
+
remainingBytes: 0,
|
|
338
|
+
deferredInUseBytes: 0,
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
let lockFd: number | null = null
|
|
343
|
+
for (let attempt = 0; attempt < 2 && lockFd === null; attempt++) {
|
|
344
|
+
try {
|
|
345
|
+
const candidateFd = fs.openSync(lockPath, 'wx')
|
|
346
|
+
try {
|
|
347
|
+
fs.writeFileSync(
|
|
348
|
+
candidateFd,
|
|
349
|
+
`${JSON.stringify({ pid: process.pid, startedAt: Date.now() })}\n`,
|
|
350
|
+
'utf8',
|
|
351
|
+
)
|
|
352
|
+
lockFd = candidateFd
|
|
353
|
+
} catch (error) {
|
|
354
|
+
fs.closeSync(candidateFd)
|
|
355
|
+
try {
|
|
356
|
+
fs.unlinkSync(lockPath)
|
|
357
|
+
} catch {}
|
|
358
|
+
throw error
|
|
359
|
+
}
|
|
360
|
+
} catch (error) {
|
|
361
|
+
const code = error instanceof Error && 'code' in error ? error.code : null
|
|
362
|
+
if (code !== 'EEXIST') throw error
|
|
363
|
+
|
|
364
|
+
let ownerPid = 0
|
|
365
|
+
let ownerStartedAt = 0
|
|
366
|
+
let originalStat: fs.Stats | null = null
|
|
367
|
+
try {
|
|
368
|
+
originalStat = fs.lstatSync(lockPath)
|
|
369
|
+
const owner = JSON.parse(fs.readFileSync(lockPath, 'utf8')) as {
|
|
370
|
+
pid?: unknown
|
|
371
|
+
startedAt?: unknown
|
|
372
|
+
}
|
|
373
|
+
ownerPid = typeof owner.pid === 'number' ? owner.pid : 0
|
|
374
|
+
ownerStartedAt = typeof owner.startedAt === 'number' ? owner.startedAt : 0
|
|
375
|
+
} catch {}
|
|
376
|
+
if (
|
|
377
|
+
ownerPid > 0 &&
|
|
378
|
+
ownerStartedAt > 0 &&
|
|
379
|
+
Date.now() - ownerStartedAt < AUTOMATIC_CLEANUP_LOCK_STALE_MS
|
|
380
|
+
) {
|
|
381
|
+
try {
|
|
382
|
+
process.kill(ownerPid, 0)
|
|
383
|
+
return {
|
|
384
|
+
status: 'busy',
|
|
385
|
+
reclaimedBytes: 0,
|
|
386
|
+
remainingBytes: 0,
|
|
387
|
+
deferredInUseBytes: 0,
|
|
388
|
+
}
|
|
389
|
+
} catch {}
|
|
390
|
+
}
|
|
391
|
+
if (!originalStat) continue
|
|
392
|
+
try {
|
|
393
|
+
const currentStat = fs.lstatSync(lockPath)
|
|
394
|
+
if (
|
|
395
|
+
currentStat.dev !== originalStat.dev ||
|
|
396
|
+
currentStat.ino !== originalStat.ino
|
|
397
|
+
) {
|
|
398
|
+
return {
|
|
399
|
+
status: 'busy',
|
|
400
|
+
reclaimedBytes: 0,
|
|
401
|
+
remainingBytes: 0,
|
|
402
|
+
deferredInUseBytes: 0,
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
fs.unlinkSync(lockPath)
|
|
406
|
+
} catch {}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (lockFd === null) {
|
|
411
|
+
return {
|
|
412
|
+
status: 'busy',
|
|
413
|
+
reclaimedBytes: 0,
|
|
414
|
+
remainingBytes: 0,
|
|
415
|
+
deferredInUseBytes: 0,
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
if (isAutomaticSootsimCleanupComplete()) {
|
|
421
|
+
return {
|
|
422
|
+
status: 'already-complete',
|
|
423
|
+
reclaimedBytes: 0,
|
|
424
|
+
remainingBytes: 0,
|
|
425
|
+
deferredInUseBytes: 0,
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const result = applySootsimCleanup()
|
|
430
|
+
const deferredInUseBytes = result.entries.reduce(
|
|
431
|
+
(total, entry) => total + (entry.state === 'in-use' ? entry.bytes : 0),
|
|
432
|
+
0,
|
|
433
|
+
)
|
|
434
|
+
if (deferredInUseBytes > 0) {
|
|
435
|
+
return {
|
|
436
|
+
status: 'deferred',
|
|
437
|
+
reclaimedBytes: result.reclaimedBytes,
|
|
438
|
+
remainingBytes: result.remainingBytes,
|
|
439
|
+
deferredInUseBytes,
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const tmp = `${completePath}.${process.pid}.tmp`
|
|
444
|
+
fs.writeFileSync(
|
|
445
|
+
tmp,
|
|
446
|
+
`${JSON.stringify({
|
|
447
|
+
generation: AUTOMATIC_CLEANUP_GENERATION,
|
|
448
|
+
completedAt: Date.now(),
|
|
449
|
+
reclaimedBytes: result.reclaimedBytes,
|
|
450
|
+
})}\n`,
|
|
451
|
+
'utf8',
|
|
452
|
+
)
|
|
453
|
+
fs.renameSync(tmp, completePath)
|
|
454
|
+
return {
|
|
455
|
+
status: 'complete',
|
|
456
|
+
reclaimedBytes: result.reclaimedBytes,
|
|
457
|
+
remainingBytes: result.remainingBytes,
|
|
458
|
+
deferredInUseBytes: 0,
|
|
459
|
+
}
|
|
460
|
+
} finally {
|
|
461
|
+
fs.closeSync(lockFd)
|
|
462
|
+
try {
|
|
463
|
+
fs.unlinkSync(lockPath)
|
|
464
|
+
} catch {}
|
|
465
|
+
}
|
|
466
|
+
}
|