sootsim 0.1.291 → 0.1.293

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.
Files changed (159) hide show
  1. package/detox/expectations.ts +32 -12
  2. package/dist-cli/bin.js +5 -5
  3. package/dist-cli/chunks/{agent-EDS4HRKB.js → agent-YWI2NWL7.js} +2 -2
  4. package/dist-cli/chunks/{agent-wrapper-ZCSAPGFF.js → agent-wrapper-IYJ7UZOA.js} +2 -2
  5. package/dist-cli/chunks/{app-fonts-4WW4EQEW.js → app-fonts-FQFUC4LR.js} +2 -2
  6. package/dist-cli/chunks/{assert-XEWAR4HT.js → assert-XUCELWAC.js} +2 -2
  7. package/dist-cli/chunks/{auth-SAYME64O.js → auth-4DQTV3OW.js} +2 -2
  8. package/dist-cli/chunks/beta-CFDAW6UN.js +2 -0
  9. package/dist-cli/chunks/{camera-2AHKTJ5I.js → camera-RYOHBVZA.js} +2 -2
  10. package/dist-cli/chunks/{chunk-7CU7FXRN.js → chunk-2M57EAWS.js} +1 -1
  11. package/dist-cli/chunks/{chunk-LJU4JU3N.js → chunk-2VKTOZQ5.js} +2 -2
  12. package/dist-cli/chunks/{chunk-NGPK2CLP.js → chunk-2XYCSFGN.js} +3 -3
  13. package/dist-cli/chunks/{chunk-UQLIMTO6.js → chunk-3UX6AY2B.js} +1 -1
  14. package/dist-cli/chunks/{chunk-R36NUSCM.js → chunk-4B4VFT73.js} +2 -2
  15. package/dist-cli/chunks/{chunk-KQKLLUX5.js → chunk-547W23BB.js} +2 -2
  16. package/dist-cli/chunks/{chunk-KSN2WTV6.js → chunk-67CIS55C.js} +1 -1
  17. package/dist-cli/chunks/{chunk-VK3IISND.js → chunk-6IFJLHDA.js} +1 -1
  18. package/dist-cli/chunks/{chunk-NRXMKMIJ.js → chunk-6VVLDYPV.js} +2 -2
  19. package/dist-cli/chunks/{chunk-LD5ZN4ZU.js → chunk-7B6RGFTM.js} +2 -2
  20. package/dist-cli/chunks/{chunk-HFJEB4UJ.js → chunk-7H34H74K.js} +2 -2
  21. package/dist-cli/chunks/{chunk-HQRKN435.js → chunk-7H5G4HUK.js} +2 -2
  22. package/dist-cli/chunks/{chunk-OC6PU44E.js → chunk-ACSTEPSD.js} +1 -1
  23. package/dist-cli/chunks/chunk-AFRXWKTA.js +118 -0
  24. package/dist-cli/chunks/{chunk-7GKJ3U2Z.js → chunk-B7TX4WFB.js} +2 -2
  25. package/dist-cli/chunks/{chunk-EFTPP4W3.js → chunk-BQT5YDTV.js} +3 -3
  26. package/dist-cli/chunks/{chunk-NPP6ZJLD.js → chunk-DE4ZA3NL.js} +2 -2
  27. package/dist-cli/chunks/{chunk-462J7KLI.js → chunk-E5WYXRGV.js} +3 -3
  28. package/dist-cli/chunks/chunk-HKMWUN7F.js +2 -0
  29. package/dist-cli/chunks/{chunk-5CJQNMXY.js → chunk-J6P2UPXH.js} +2 -2
  30. package/dist-cli/chunks/{chunk-GJDCBMAP.js → chunk-JOPKZVOW.js} +1 -1
  31. package/dist-cli/chunks/{chunk-EPJO7YNS.js → chunk-K5JEUI5G.js} +1 -1
  32. package/dist-cli/chunks/{chunk-5D5XSE5V.js → chunk-KC5SCOGS.js} +2 -2
  33. package/dist-cli/chunks/{chunk-FWC4J42U.js → chunk-LA5DIO33.js} +1 -1
  34. package/dist-cli/chunks/{chunk-653O3F3M.js → chunk-LOQLZFRK.js} +2 -2
  35. package/dist-cli/chunks/{chunk-QOIOK64H.js → chunk-MOU2WLO4.js} +1 -1
  36. package/dist-cli/chunks/{chunk-LHBU5QMM.js → chunk-NCCMRFXU.js} +1 -1
  37. package/dist-cli/chunks/{chunk-5IBT7JOS.js → chunk-PCAPHXN3.js} +2 -2
  38. package/dist-cli/chunks/chunk-QXDXUDJD.js +1 -0
  39. package/dist-cli/chunks/{chunk-NN4CZ2QW.js → chunk-RKXHQ2Q3.js} +1 -1
  40. package/dist-cli/chunks/{chunk-Z6I5M7VJ.js → chunk-RMLJXP7T.js} +2 -2
  41. package/dist-cli/chunks/chunk-RV67J726.js +2 -0
  42. package/dist-cli/chunks/{chunk-UZA3Q67P.js → chunk-TJS5IMOH.js} +1 -1
  43. package/dist-cli/chunks/{chunk-KTTLGU7Y.js → chunk-U3564F2Y.js} +2 -2
  44. package/dist-cli/chunks/{chunk-3FBFOGQJ.js → chunk-UOHGYYSO.js} +1 -1
  45. package/dist-cli/chunks/{chunk-DLMAMKWL.js → chunk-UPKYK52K.js} +2 -2
  46. package/dist-cli/chunks/{chunk-GZON2OYB.js → chunk-USJTVC7T.js} +1 -1
  47. package/dist-cli/chunks/{chunk-U6GFEFUD.js → chunk-VO74GDXL.js} +3 -3
  48. package/dist-cli/chunks/{chunk-76O3ZWTI.js → chunk-VZ44FQDG.js} +1 -1
  49. package/dist-cli/chunks/chunk-WI36YJ23.js +2 -0
  50. package/dist-cli/chunks/{chunk-5NGZRMTN.js → chunk-WOWLO4LL.js} +1 -1
  51. package/dist-cli/chunks/chunk-WP3TFHFL.js +10 -0
  52. package/dist-cli/chunks/{chunk-KBG6WN24.js → chunk-WPPMUUML.js} +2 -2
  53. package/dist-cli/chunks/{chunk-ZYS5SEB2.js → chunk-WVMYRKSS.js} +2 -2
  54. package/dist-cli/chunks/chunk-YNG4C3ZS.js +2 -0
  55. package/dist-cli/chunks/{chunk-JTZNB4PB.js → chunk-YRFWATLA.js} +2 -2
  56. package/dist-cli/chunks/{cleanup-KXX5W42A.js → cleanup-BLFDJSFS.js} +2 -2
  57. package/dist-cli/chunks/cli-version-TQHZOK4L.js +2 -0
  58. package/dist-cli/chunks/{compat-VDVJ2I74.js → compat-Q3WCSTB7.js} +3 -3
  59. package/dist-cli/chunks/{config-FIQQP5FR.js → config-NHNMRRNB.js} +2 -2
  60. package/dist-cli/chunks/control-I7YBDRJT.js +2 -0
  61. package/dist-cli/chunks/daemon-4R3SZLZY.js +2 -0
  62. package/dist-cli/chunks/{debug-LPLH46JF.js → debug-PGRMZFXA.js} +3 -3
  63. package/dist-cli/chunks/{desktop-4HDRWKMN.js → desktop-NVTQ37LH.js} +3 -3
  64. package/dist-cli/chunks/{detox-U23WKW23.js → detox-W3JDXS76.js} +2 -2
  65. package/dist-cli/chunks/{device-HZKTA7ZV.js → device-KQYN3EVK.js} +2 -2
  66. package/dist-cli/chunks/{diagnose-M64QUWBW.js → diagnose-CPUKSGNO.js} +2 -2
  67. package/dist-cli/chunks/disk-cleanup-2COWZWBC.js +2 -0
  68. package/dist-cli/chunks/drivers-RRZM6E72.js +2 -0
  69. package/dist-cli/chunks/{film-4IB44CT3.js → film-VZGAZ2YY.js} +3 -3
  70. package/dist-cli/chunks/flow-VH6DFDQ4.js +2 -0
  71. package/dist-cli/chunks/help-3HQ2RINO.js +2 -0
  72. package/dist-cli/chunks/{hidden-runtime-alias-UEU5POA4.js → hidden-runtime-alias-KWHABCNZ.js} +2 -2
  73. package/dist-cli/chunks/{home-paths-2C4YKPPX.js → home-paths-O4CGYVHF.js} +2 -2
  74. package/dist-cli/chunks/inspect-PKMJGXDH.js +2 -0
  75. package/dist-cli/chunks/install-desktop-K5TP3ZHJ.js +2 -0
  76. package/dist-cli/chunks/{login-MY54AOXW.js → login-KPN4Z26Q.js} +4 -4
  77. package/dist-cli/chunks/{logout-LYR2RDJY.js → logout-E3UCJAMC.js} +2 -2
  78. package/dist-cli/chunks/{maestro-4RAMCNZQ.js → maestro-PYA4AHTS.js} +3 -3
  79. package/dist-cli/chunks/{maestro-generate-CBSUR73A.js → maestro-generate-KCMYWPWL.js} +3 -3
  80. package/dist-cli/chunks/{mode-2KATMMXB.js → mode-AEB7G2SJ.js} +2 -2
  81. package/dist-cli/chunks/optional-demo-registry-AIWOBAAA.js +2 -0
  82. package/dist-cli/chunks/{perf-FVL2ZRQW.js → perf-YLXGZI3E.js} +2 -2
  83. package/dist-cli/chunks/permissions-7VMVZOUV.js +11 -0
  84. package/dist-cli/chunks/{record-TCD4VLNH.js → record-AVQO45PE.js} +3 -3
  85. package/dist-cli/chunks/runtime-HOGETVVD.js +2 -0
  86. package/dist-cli/chunks/{screenshot-command-ENCJ4W2W.js → screenshot-command-LACQLA4N.js} +3 -3
  87. package/dist-cli/chunks/{screenshots-capture-FVVMZPFO.js → screenshots-capture-PROOBSGB.js} +2 -2
  88. package/dist-cli/chunks/{serve-HNHS3QBP.js → serve-FJN2FRGW.js} +2 -2
  89. package/dist-cli/chunks/{setup-MRLIGVU4.js → setup-IVV4NRYQ.js} +3 -3
  90. package/dist-cli/chunks/{skills-2LW5BE7P.js → skills-WYR3PJOH.js} +2 -2
  91. package/dist-cli/chunks/{storage-KH5HQF3V.js → storage-GM7K4MGM.js} +2 -2
  92. package/dist-cli/chunks/store-YLVHPKG7.js +2 -0
  93. package/dist-cli/chunks/telemetry-MQKB6PFX.js +2 -0
  94. package/dist-cli/chunks/{timeline-BWFBU4VM.js → timeline-R5UL3N4T.js} +2 -2
  95. package/dist-cli/chunks/{upgrade-GZFX4JEO.js → upgrade-RWHSFZRX.js} +2 -2
  96. package/dist-cli/chunks/upload-RBH4XGJ2.js +2 -0
  97. package/dist-cli/chunks/{version-OKMVZCP5.js → version-7K6TVQOW.js} +2 -2
  98. package/dist-cli/chunks/web-46ZPQCAL.js +2 -0
  99. package/dist-cli/chunks/{what-happened-MC7JDW6C.js → what-happened-K24TSJZX.js} +2 -2
  100. package/dist-lib/agent-daemon-client.cjs +1 -1
  101. package/dist-lib/agent-events.cjs +1 -1
  102. package/dist-lib/agent-identity.cjs +1 -1
  103. package/dist-lib/agent-sessions.cjs +1 -1
  104. package/dist-lib/attached-projects.cjs +1 -1
  105. package/dist-lib/auth/shared-session.cjs +1 -1
  106. package/dist-lib/backend-origin.cjs +1 -1
  107. package/dist-lib/beta.cjs +1 -1
  108. package/dist-lib/beta.mjs +1 -1
  109. package/dist-lib/bridge-constants.cjs +1 -1
  110. package/dist-lib/cli-constants.cjs +1 -1
  111. package/dist-lib/config.cjs +1 -1
  112. package/dist-lib/detox/index.cjs +24 -6
  113. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  114. package/dist-lib/home-paths.cjs +1 -1
  115. package/dist-lib/host/bridge-host.cjs +411 -66
  116. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  117. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  118. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  119. package/dist-lib/host/websocket-proxy.cjs +1 -1
  120. package/dist-lib/index.cjs +1 -1
  121. package/dist-lib/jump-to-source-babel.cjs +1 -1
  122. package/dist-lib/menu.cjs +38 -2
  123. package/dist-lib/menu.mjs +38 -2
  124. package/dist-lib/metro.cjs +1 -1
  125. package/dist-lib/profiles.cjs +1 -1
  126. package/dist-lib/render-mode.cjs +1 -1
  127. package/dist-lib/scripts/dev-server-scanner.cjs +354 -2
  128. package/dist-lib/sdk.cjs +1 -1
  129. package/dist-lib/sdk.mjs +1 -1
  130. package/dist-lib/skills.cjs +443 -91
  131. package/dist-lib/vite.cjs +1 -1
  132. package/package.json +2 -1
  133. package/scripts/dev-server-scanner.ts +27 -1
  134. package/skills/contrast/SKILL.md +1 -0
  135. package/src/menu.ts +38 -1
  136. package/src/settings-bundle.ts +439 -0
  137. package/dist-cli/chunks/beta-HKVUNMLY.js +0 -2
  138. package/dist-cli/chunks/chunk-5ZBUNUUE.js +0 -2
  139. package/dist-cli/chunks/chunk-7NW75BHD.js +0 -5
  140. package/dist-cli/chunks/chunk-H7F2BILD.js +0 -118
  141. package/dist-cli/chunks/chunk-IDZP6WN5.js +0 -2
  142. package/dist-cli/chunks/chunk-IGPDVTHJ.js +0 -2
  143. package/dist-cli/chunks/chunk-KU3T3N2W.js +0 -1
  144. package/dist-cli/chunks/chunk-ZEYCE3XJ.js +0 -2
  145. package/dist-cli/chunks/cli-version-IQXVYN67.js +0 -2
  146. package/dist-cli/chunks/control-GDBZBVQC.js +0 -2
  147. package/dist-cli/chunks/daemon-Y76JSFGV.js +0 -2
  148. package/dist-cli/chunks/disk-cleanup-P662TT6J.js +0 -2
  149. package/dist-cli/chunks/drivers-CKWEDYVG.js +0 -2
  150. package/dist-cli/chunks/flow-WDFYSQKC.js +0 -2
  151. package/dist-cli/chunks/help-2S3OHQLT.js +0 -2
  152. package/dist-cli/chunks/inspect-SKU24VE3.js +0 -2
  153. package/dist-cli/chunks/install-desktop-RKURHJ34.js +0 -2
  154. package/dist-cli/chunks/optional-demo-registry-26Q7AW7X.js +0 -2
  155. package/dist-cli/chunks/runtime-Z3D53UGW.js +0 -2
  156. package/dist-cli/chunks/store-IBEI6PK4.js +0 -2
  157. package/dist-cli/chunks/telemetry-3T56HLDZ.js +0 -2
  158. package/dist-cli/chunks/upload-PCXRS5D7.js +0 -2
  159. package/dist-cli/chunks/web-ZHCP6WFY.js +0 -2
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.291 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.293 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sootsim",
3
- "version": "0.1.291",
3
+ "version": "0.1.293",
4
4
  "description": "sootsim CLI + vite/metro plugins + skills registry. bridge client for driving the proprietary sootsim-engine over WebSocket.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "MIT",
@@ -163,6 +163,7 @@
163
163
  "preuninstall": "node ./scripts/preuninstall.cjs"
164
164
  },
165
165
  "dependencies": {
166
+ "@expo/plist": "^0.5.4",
166
167
  "ws": "^8.18.0"
167
168
  },
168
169
  "peerDependencies": {
@@ -17,10 +17,15 @@ import { exec } from 'child_process'
17
17
  import http from 'http'
18
18
  import net from 'net'
19
19
  import { promisify } from 'util'
20
+ import type { AppSettingsSpecifier } from '@sootsim/globals'
20
21
 
21
22
  const execP = promisify(exec)
22
23
  import { applySootSimConfigToUrl } from '../src/config.ts'
23
24
  import { normalizeNativeDevBundleUrl } from '../src/native-dev-bundle-url.ts'
25
+ import {
26
+ __resetAppSettingsBundleCacheForTests,
27
+ readAppSettingsBundle,
28
+ } from '../src/settings-bundle.ts'
24
29
  import { loadOptionalDemoApps, optionalDemoAppsSync } from './optional-demo-registry.ts'
25
30
 
26
31
  // pre-warm the optional internal demo registry once at module load. in-repo
@@ -41,6 +46,8 @@ export interface DiscoveredServer {
41
46
  iconUrl?: string
42
47
  iconPath?: string
43
48
  bundleId?: string
49
+ settingsBundle?: readonly AppSettingsSpecifier[]
50
+ settingsStorageScopeUrl?: string
44
51
  patched?: boolean
45
52
  /** absolute cwd of the owning node/bun process — resolved from `lsof -d cwd`
46
53
  * when the scanner can see the pid. used to auto-attach the project for
@@ -709,8 +716,25 @@ export function __resetScannerCache() {
709
716
  knownOne.clear()
710
717
  enrichmentCache.clear()
711
718
  enrichmentInFlight.clear()
719
+ __resetAppSettingsBundleCacheForTests()
712
720
  }
713
721
 
722
+ async function attachAppSettingsBundle(
723
+ result: DiscoveredServer,
724
+ projectRoot: string,
725
+ ): Promise<void> {
726
+ const settingsBundle = await readAppSettingsBundle(projectRoot)
727
+ if (settingsBundle) {
728
+ result.settingsBundle = settingsBundle
729
+ result.settingsStorageScopeUrl = result.bundleUrl
730
+ } else {
731
+ delete result.settingsBundle
732
+ delete result.settingsStorageScopeUrl
733
+ }
734
+ }
735
+
736
+ export const __attachAppSettingsBundleForTests = attachAppSettingsBundle
737
+
714
738
  export async function scanDevServers(
715
739
  opts: ScanOptions = {},
716
740
  ): Promise<DiscoveredServer[]> {
@@ -796,7 +820,9 @@ export async function scanDevServers(
796
820
  if (!pid) return
797
821
  result.pid = pid
798
822
  const cwd = await resolveProcessCwd(pid)
799
- if (cwd) result.cwd = cwd
823
+ if (!cwd) return
824
+ result.cwd = cwd
825
+ await attachAppSettingsBundle(result, cwd)
800
826
  }),
801
827
  )
802
828
  // evict stale cwd cache entries for pids no longer listening
@@ -25,6 +25,7 @@ automation, inspecting the render tree, and managing sims.
25
25
  | `sootsim record` | capture the live sim as webm/mp4/gif, or sample N png frames (--frames) |
26
26
  | `sootsim film` | record a flow on the flat canvas, replay it on the 3d device stage under a camera cinematic, save a clip |
27
27
  | `sootsim storage` | manage guest app storage and launch profiles |
28
+ | `sootsim permissions` | inspect and change simulated operating-system permissions |
28
29
  | `sootsim perf` | CPU, React render, shell frame, and scroll-layer profiling |
29
30
  | `sootsim describe` | curated human-readable UI summary (default inspection tool) |
30
31
  | `sootsim find` | locate nodes by text / testID / role / type / predicate |
package/src/menu.ts CHANGED
@@ -36,6 +36,7 @@ export interface SootSimMenuState {
36
36
  currentDevice: string
37
37
  showBezels: boolean
38
38
  showTopBar: boolean
39
+ showRail: boolean
39
40
  screenshotMode: boolean
40
41
  threeMode: boolean
41
42
  inspectMode: boolean
@@ -107,6 +108,36 @@ function appMenu(): SootSimMenuDef {
107
108
  shortcutLabel: 'settings',
108
109
  showInShortcutDocs: true,
109
110
  },
111
+ {
112
+ label: 'Settings Pages',
113
+ items: [
114
+ {
115
+ label: 'Accessibility',
116
+ action: 'open-settings-page',
117
+ actionPayload: 'accessibility',
118
+ },
119
+ {
120
+ label: 'Display & Text Size',
121
+ action: 'open-settings-page',
122
+ actionPayload: 'display-and-text-size',
123
+ },
124
+ {
125
+ label: 'Notifications',
126
+ action: 'open-settings-page',
127
+ actionPayload: 'notifications',
128
+ },
129
+ {
130
+ label: 'Apps',
131
+ action: 'open-settings-page',
132
+ actionPayload: 'apps',
133
+ },
134
+ {
135
+ label: 'Privacy & Security',
136
+ action: 'open-settings-page',
137
+ actionPayload: 'privacy',
138
+ },
139
+ ],
140
+ },
110
141
  { type: 'separator' },
111
142
  {
112
143
  label: 'Hide SootSim',
@@ -338,9 +369,15 @@ function windowMenu(state: SootSimMenuState): SootSimMenuDef {
338
369
  label: 'Show Top Bar',
339
370
  type: 'checkbox',
340
371
  checked: state.showTopBar,
341
- enabled: state.showBezels,
342
372
  action: 'toggle-topbar',
343
373
  },
374
+ {
375
+ label: 'Show Rail',
376
+ type: 'checkbox',
377
+ checked: state.showRail,
378
+ action: 'toggle-rail',
379
+ hiddenIn: 'electron',
380
+ },
344
381
  {
345
382
  label: 'Screenshot',
346
383
  type: 'checkbox',
@@ -0,0 +1,439 @@
1
+ import { readFile, readdir, stat } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+ import plistModule from '@expo/plist'
4
+ import type { AppPreferenceValue, AppSettingsSpecifier } from '@sootsim/globals'
5
+
6
+ const MAX_PLIST_BYTES = 1_048_576
7
+ const MAX_CHILD_DEPTH = 8
8
+ const MAX_PLIST_FILES = 32
9
+ const MAX_SPECIFIERS = 512
10
+
11
+ type PlistRecord = Record<string, unknown>
12
+
13
+ interface ParseBudget {
14
+ files: number
15
+ specifiers: number
16
+ }
17
+
18
+ interface CachedSettingsBundle {
19
+ fingerprint: string
20
+ specifiers: readonly AppSettingsSpecifier[] | null
21
+ }
22
+
23
+ const settingsBundleCache = new Map<string, CachedSettingsBundle>()
24
+
25
+ function isRecord(value: unknown): value is PlistRecord {
26
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
27
+ }
28
+
29
+ function resolvePlistParser(value: unknown): ((source: string) => unknown) | null {
30
+ if (!isRecord(value)) return null
31
+ const direct = value.parse
32
+ if (typeof direct === 'function') {
33
+ return (source) => Reflect.apply(direct, value, [source])
34
+ }
35
+ const nested = value.default
36
+ if (!isRecord(nested)) return null
37
+ const nestedParse = nested.parse
38
+ return typeof nestedParse === 'function'
39
+ ? (source) => Reflect.apply(nestedParse, nested, [source])
40
+ : null
41
+ }
42
+
43
+ // bun consumes @expo/plist's default export directly, while the compiled commonjs
44
+ // scanner receives the package namespace with that export nested under
45
+ // `default`. resolve the parser once at this package boundary so source and
46
+ // published scanners execute the same implementation.
47
+ const parsePlist = resolvePlistParser(plistModule)
48
+
49
+ function optionalString(value: unknown): string | undefined {
50
+ return typeof value === 'string' ? value : undefined
51
+ }
52
+
53
+ function requiredString(value: unknown): string | null {
54
+ if (typeof value !== 'string') return null
55
+ const normalized = value.trim()
56
+ return normalized ? normalized : null
57
+ }
58
+
59
+ function finiteNumber(value: unknown): number | null {
60
+ return typeof value === 'number' && Number.isFinite(value) ? value : null
61
+ }
62
+
63
+ function preferenceValue(value: unknown): AppPreferenceValue | null {
64
+ if (typeof value === 'string' || typeof value === 'boolean') return value
65
+ return finiteNumber(value)
66
+ }
67
+
68
+ function keyboardType(
69
+ value: unknown,
70
+ ): Extract<AppSettingsSpecifier, { type: 'text' }>['keyboardType'] {
71
+ if (typeof value !== 'string') return undefined
72
+ switch (value.replaceAll(/[-_\s]/g, '').toLowerCase()) {
73
+ case 'alphabet':
74
+ return 'alphabet'
75
+ case 'numbersandpunctuation':
76
+ return 'numbers-and-punctuation'
77
+ case 'numberpad':
78
+ return 'number-pad'
79
+ case 'url':
80
+ return 'url'
81
+ case 'emailaddress':
82
+ return 'email-address'
83
+ case 'namephonepad':
84
+ return 'name-phone-pad'
85
+ case 'asciicapable':
86
+ return 'ascii-capable'
87
+ default:
88
+ return undefined
89
+ }
90
+ }
91
+
92
+ function autocapitalization(
93
+ value: unknown,
94
+ ): Extract<AppSettingsSpecifier, { type: 'text' }>['autocapitalization'] {
95
+ if (typeof value !== 'string') return undefined
96
+ switch (value.replaceAll(/[-_\s]/g, '').toLowerCase()) {
97
+ case 'none':
98
+ return 'none'
99
+ case 'sentences':
100
+ return 'sentences'
101
+ case 'words':
102
+ return 'words'
103
+ case 'allcharacters':
104
+ return 'all-characters'
105
+ default:
106
+ return undefined
107
+ }
108
+ }
109
+
110
+ function autocorrection(value: unknown): boolean | undefined {
111
+ if (typeof value === 'boolean') return value
112
+ if (typeof value !== 'string') return undefined
113
+ switch (value.trim().toLowerCase()) {
114
+ case 'yes':
115
+ return true
116
+ case 'no':
117
+ return false
118
+ default:
119
+ return undefined
120
+ }
121
+ }
122
+
123
+ function safeChildPlistName(value: unknown): { file: string; name: string } | null {
124
+ const file = requiredString(value)
125
+ if (!file || file.includes('/') || file.includes('\\') || file.includes('..')) {
126
+ return null
127
+ }
128
+ return {
129
+ file,
130
+ name: file.toLowerCase().endsWith('.plist') ? file : `${file}.plist`,
131
+ }
132
+ }
133
+
134
+ async function readableSettingsBundle(path: string): Promise<boolean> {
135
+ try {
136
+ const root = await stat(join(path, 'Root.plist'))
137
+ return root.isFile() && root.size <= MAX_PLIST_BYTES
138
+ } catch {
139
+ return false
140
+ }
141
+ }
142
+
143
+ async function findSettingsBundle(projectRoot: string): Promise<string | null> {
144
+ const directCandidates = [
145
+ join(projectRoot, 'Settings.bundle'),
146
+ join(projectRoot, 'ios', 'Settings.bundle'),
147
+ ]
148
+ for (const candidate of directCandidates) {
149
+ if (await readableSettingsBundle(candidate)) return candidate
150
+ }
151
+
152
+ try {
153
+ const iosRoot = join(projectRoot, 'ios')
154
+ const children = await readdir(iosRoot, { withFileTypes: true })
155
+ const candidates = children
156
+ .filter(
157
+ (entry) =>
158
+ entry.isDirectory() &&
159
+ entry.name !== 'Pods' &&
160
+ entry.name !== 'build' &&
161
+ !entry.name.startsWith('.'),
162
+ )
163
+ .map((entry) => join(iosRoot, entry.name, 'Settings.bundle'))
164
+ .sort()
165
+ for (const candidate of candidates) {
166
+ if (await readableSettingsBundle(candidate)) return candidate
167
+ }
168
+ } catch {}
169
+
170
+ return null
171
+ }
172
+
173
+ async function settingsBundleFingerprint(bundlePath: string): Promise<string | null> {
174
+ try {
175
+ const entries = await readdir(bundlePath, { withFileTypes: true })
176
+ const names = entries
177
+ .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.plist'))
178
+ .map((entry) => entry.name)
179
+ .sort()
180
+ .slice(0, MAX_PLIST_FILES + 1)
181
+ if (names.length === 0 || names.length > MAX_PLIST_FILES) return null
182
+
183
+ const parts = await Promise.all(
184
+ names.map(async (name) => {
185
+ const metadata = await stat(join(bundlePath, name))
186
+ if (!metadata.isFile() || metadata.size > MAX_PLIST_BYTES) return null
187
+ return `${name}:${metadata.size}:${metadata.mtimeMs}:${metadata.ctimeMs}`
188
+ }),
189
+ )
190
+ if (parts.some((part) => part === null)) return null
191
+ return parts.join('|')
192
+ } catch {
193
+ return null
194
+ }
195
+ }
196
+
197
+ async function parsePlistRecord(path: string): Promise<PlistRecord | null> {
198
+ try {
199
+ if (!parsePlist) return null
200
+ const metadata = await stat(path)
201
+ if (!metadata.isFile() || metadata.size > MAX_PLIST_BYTES) return null
202
+ const source = await readFile(path, 'utf8')
203
+ const parsed = parsePlist(source)
204
+ return isRecord(parsed) ? parsed : null
205
+ } catch {
206
+ return null
207
+ }
208
+ }
209
+
210
+ function normalizeMultiValue(
211
+ raw: PlistRecord,
212
+ key: string,
213
+ title: string,
214
+ ): AppSettingsSpecifier | null {
215
+ if (!Array.isArray(raw.Values) || !Array.isArray(raw.Titles)) return null
216
+ const values: AppPreferenceValue[] = []
217
+ const titles: string[] = []
218
+ let optionType: 'string' | 'number' | 'boolean' | undefined
219
+ const pairCount = Math.min(raw.Values.length, raw.Titles.length)
220
+ for (let index = 0; index < pairCount; index++) {
221
+ const value = preferenceValue(raw.Values[index])
222
+ const optionTitle = optionalString(raw.Titles[index])
223
+ if (value === null || optionTitle === undefined) continue
224
+ const valueType: 'string' | 'number' | 'boolean' =
225
+ typeof value === 'string'
226
+ ? 'string'
227
+ : typeof value === 'number'
228
+ ? 'number'
229
+ : 'boolean'
230
+ if (optionType !== undefined && valueType !== optionType) continue
231
+ optionType = valueType
232
+ values.push(value)
233
+ titles.push(optionTitle)
234
+ }
235
+ const firstValue = values[0]
236
+ if (firstValue === undefined) return null
237
+ const declaredDefault = preferenceValue(raw.DefaultValue)
238
+ const defaultValue =
239
+ declaredDefault !== null && values.includes(declaredDefault)
240
+ ? declaredDefault
241
+ : firstValue
242
+ return {
243
+ type: 'multi-value',
244
+ key,
245
+ title,
246
+ defaultValue,
247
+ currentValue: defaultValue,
248
+ values,
249
+ titles,
250
+ }
251
+ }
252
+
253
+ async function normalizeSpecifier(
254
+ raw: unknown,
255
+ context: {
256
+ bundlePath: string
257
+ depth: number
258
+ budget: ParseBudget
259
+ stack: ReadonlySet<string>
260
+ fileCache: Map<string, readonly AppSettingsSpecifier[] | null>
261
+ },
262
+ ): Promise<AppSettingsSpecifier | null> {
263
+ if (!isRecord(raw)) return null
264
+ if (context.budget.specifiers >= MAX_SPECIFIERS) return null
265
+ const type = requiredString(raw.Type)
266
+ if (!type) return null
267
+ context.budget.specifiers += 1
268
+
269
+ if (type === 'PSGroupSpecifier') {
270
+ const title = optionalString(raw.Title)
271
+ const footer = optionalString(raw.FooterText)
272
+ return {
273
+ type: 'group',
274
+ ...(title !== undefined ? { title } : null),
275
+ ...(footer !== undefined ? { footer } : null),
276
+ }
277
+ }
278
+
279
+ const key = requiredString(raw.Key)
280
+ if (type === 'PSSliderSpecifier') {
281
+ if (!key) return null
282
+ const minimumValue = finiteNumber(raw.MinimumValue)
283
+ const maximumValue = finiteNumber(raw.MaximumValue)
284
+ if (minimumValue === null || maximumValue === null || maximumValue <= minimumValue) {
285
+ return null
286
+ }
287
+ const declaredDefault = finiteNumber(raw.DefaultValue)
288
+ const defaultValue = Math.min(
289
+ maximumValue,
290
+ Math.max(minimumValue, declaredDefault ?? minimumValue),
291
+ )
292
+ const title = optionalString(raw.Title)
293
+ const minimumValueImage = optionalString(raw.MinimumValueImage)
294
+ const maximumValueImage = optionalString(raw.MaximumValueImage)
295
+ return {
296
+ type: 'slider',
297
+ key,
298
+ defaultValue,
299
+ currentValue: defaultValue,
300
+ minimumValue,
301
+ maximumValue,
302
+ ...(title !== undefined ? { title } : null),
303
+ ...(minimumValueImage !== undefined ? { minimumValueImage } : null),
304
+ ...(maximumValueImage !== undefined ? { maximumValueImage } : null),
305
+ }
306
+ }
307
+
308
+ if (type === 'PSChildPaneSpecifier') {
309
+ const title = requiredString(raw.Title)
310
+ const child = safeChildPlistName(raw.File)
311
+ if (!title || !child || context.depth >= MAX_CHILD_DEPTH) return null
312
+ const childSpecifiers = await parseSpecifierFile(child.name, {
313
+ ...context,
314
+ depth: context.depth + 1,
315
+ })
316
+ if (!childSpecifiers || childSpecifiers.length === 0) return null
317
+ return {
318
+ type: 'child-pane',
319
+ title,
320
+ file: child.file,
321
+ specifiers: childSpecifiers,
322
+ }
323
+ }
324
+
325
+ if (!key) return null
326
+ const title = requiredString(raw.Title) ?? key
327
+
328
+ if (type === 'PSToggleSwitchSpecifier') {
329
+ const defaultValue = typeof raw.DefaultValue === 'boolean' ? raw.DefaultValue : false
330
+ return {
331
+ type: 'toggle',
332
+ key,
333
+ title,
334
+ defaultValue,
335
+ currentValue: defaultValue,
336
+ }
337
+ }
338
+
339
+ if (type === 'PSTextFieldSpecifier') {
340
+ const defaultValue = optionalString(raw.DefaultValue) ?? ''
341
+ const placeholder = optionalString(raw.Placeholder)
342
+ const resolvedKeyboardType = keyboardType(raw.KeyboardType)
343
+ const resolvedAutocapitalization = autocapitalization(raw.AutocapitalizationType)
344
+ const resolvedAutocorrection = autocorrection(raw.AutocorrectionType)
345
+ return {
346
+ type: 'text',
347
+ key,
348
+ title,
349
+ defaultValue,
350
+ currentValue: defaultValue,
351
+ ...(placeholder !== undefined ? { placeholder } : null),
352
+ ...(typeof raw.IsSecure === 'boolean' ? { secure: raw.IsSecure } : null),
353
+ ...(resolvedKeyboardType ? { keyboardType: resolvedKeyboardType } : null),
354
+ ...(resolvedAutocapitalization
355
+ ? { autocapitalization: resolvedAutocapitalization }
356
+ : null),
357
+ ...(resolvedAutocorrection !== undefined
358
+ ? { autocorrection: resolvedAutocorrection }
359
+ : null),
360
+ }
361
+ }
362
+
363
+ if (type === 'PSMultiValueSpecifier' || type === 'PSRadioGroupSpecifier') {
364
+ return normalizeMultiValue(raw, key, title)
365
+ }
366
+
367
+ if (type === 'PSTitleValueSpecifier') {
368
+ const defaultValue = preferenceValue(raw.DefaultValue) ?? ''
369
+ return {
370
+ type: 'title-value',
371
+ key,
372
+ title,
373
+ defaultValue,
374
+ currentValue: defaultValue,
375
+ }
376
+ }
377
+
378
+ return null
379
+ }
380
+
381
+ async function parseSpecifierFile(
382
+ fileName: string,
383
+ context: {
384
+ bundlePath: string
385
+ depth: number
386
+ budget: ParseBudget
387
+ stack: ReadonlySet<string>
388
+ fileCache: Map<string, readonly AppSettingsSpecifier[] | null>
389
+ },
390
+ ): Promise<readonly AppSettingsSpecifier[] | null> {
391
+ const cached = context.fileCache.get(fileName)
392
+ if (cached !== undefined) return cached
393
+ if (context.stack.has(fileName) || context.budget.files >= MAX_PLIST_FILES) {
394
+ return null
395
+ }
396
+ context.budget.files += 1
397
+ const root = await parsePlistRecord(join(context.bundlePath, fileName))
398
+ if (!root || !Array.isArray(root.PreferenceSpecifiers)) {
399
+ context.fileCache.set(fileName, null)
400
+ return null
401
+ }
402
+
403
+ const stack = new Set(context.stack)
404
+ stack.add(fileName)
405
+ const specifiers: AppSettingsSpecifier[] = []
406
+ for (const raw of root.PreferenceSpecifiers) {
407
+ if (context.budget.specifiers >= MAX_SPECIFIERS) break
408
+ const specifier = await normalizeSpecifier(raw, { ...context, stack })
409
+ if (specifier) specifiers.push(specifier)
410
+ }
411
+ context.fileCache.set(fileName, specifiers)
412
+ return specifiers
413
+ }
414
+
415
+ export async function readAppSettingsBundle(
416
+ projectRoot: string,
417
+ ): Promise<readonly AppSettingsSpecifier[] | null> {
418
+ const bundlePath = await findSettingsBundle(projectRoot)
419
+ if (!bundlePath) return null
420
+ const fingerprint = await settingsBundleFingerprint(bundlePath)
421
+ if (!fingerprint) return null
422
+ const cached = settingsBundleCache.get(bundlePath)
423
+ if (cached?.fingerprint === fingerprint) return cached.specifiers
424
+
425
+ const specifiers = await parseSpecifierFile('Root.plist', {
426
+ bundlePath,
427
+ depth: 0,
428
+ budget: { files: 0, specifiers: 0 },
429
+ stack: new Set(),
430
+ fileCache: new Map(),
431
+ })
432
+ const normalized = specifiers && specifiers.length > 0 ? specifiers : null
433
+ settingsBundleCache.set(bundlePath, { fingerprint, specifiers: normalized })
434
+ return normalized
435
+ }
436
+
437
+ export function __resetAppSettingsBundleCacheForTests(): void {
438
+ settingsBundleCache.clear()
439
+ }
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.291 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b,c,d}from"./chunk-GZON2OYB.js";import"./chunk-ZEYCE3XJ.js";export{d as BETA_ASK_HEADLINE,b as BETA_LABEL,c as BETA_TAGLINE,a as IS_BETA};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.291 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{c as k,k as l,l as C,m as w}from"./chunk-IDZP6WN5.js";var m="sootsim:settings",a=["errorReporting","colorScheme","reduceMotion","a11yMode","a11yDepth","screenshotCanvas","screenshotBackground","screenshotText","screenshotPose","screenshotDynamicSize","alwaysShowLiveWebViews","agentProvider","onboardingComplete","betaConsentVersion","detailedTelemetry"];function R(){if(typeof window>"u")return null;let e=window.sootsimElectron;return e&&typeof e=="object"?e:null}function g(e){let n={};for(let t of a){if(!(t in e))continue;let o=e[t],i=l[t];i.validate&&!i.validate(o)||i.options&&!i.options.includes(o)||(n[t]=o)}return n}function V(e){let n={};for(let t of a)n[t]=e[t];return n}function D(){if(typeof localStorage>"u")return{};try{let e=localStorage.getItem(m);return e?g(JSON.parse(e)):{}}catch{return{}}}function j(e){if(!(typeof localStorage>"u"))try{localStorage.setItem(m,JSON.stringify(e))}catch{}}function x(){let n=R()?.sharedConfig?.settings;return!n||typeof n!="object"?{}:g(n)}function M(e){let n=R();if(n?.writeSharedConfig)try{n.writeSharedConfig({settings:e})}catch{}}function K(){if(typeof window>"u")return null;let e=window.__sootsimSharedConfig;return e&&typeof e=="object"?e:null}function T(){let e=K()?.settings;return!e||typeof e!="object"?{}:g(e)}function I(e){if(!K())return;let n={settings:e};try{if(globalThis.__sootsimBridgeRequest?.writeSharedConfig?.(n))return}catch{}if(!(typeof fetch>"u"))try{fetch("/__sootsim/shared-config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n),keepalive:!0}).catch(()=>{})}catch{}}function P(){let e=D(),n=T(),t=x();return{...e,...n,...t}}function _(e,n={}){let t=V(e);for(let o of a)n[o]!==void 0&&(t[o]=n[o]);j(t),M(t),I(t)}function E(e){if(typeof window>"u")return()=>{};let n=r=>{if(!(r.key!==m||r.newValue==null))try{e(g(JSON.parse(r.newValue)))}catch{}};window.addEventListener("storage",n);let t=R(),o;t?.onSharedConfigChanged&&(o=t.onSharedConfigChanged(r=>{let d=r?.settings;!d||typeof d!="object"||e(g(d))}));let c=globalThis.__sootsimBridgeRequest?.subscribeSharedConfig?.(r=>{let d=r?.settings;!d||typeof d!="object"||e(g(d))});return()=>{window.removeEventListener("storage",n),o?.(),c?.()}}var O=C(),B=w(),p=P(),u=new Set,S={};for(let e of a)O[e]!==void 0&&(u.add(e),p[e]!==void 0&&(S[e]=p[e]),delete p[e]);var s={...B,...p},h=[],f=null;function y(){for(let e of h)e()}function v(){y(),_(s,S)}function b(e,n){let t=l[e];return t.validate&&!t.validate(n)||t.options&&!t.options.includes(n)||s[e]===n?!1:(s={...s,[e]:n},e==="deviceModel"&&(f=null),!0)}E(e=>{let n={...s},t=!1;for(let[o,i]of Object.entries(e)){let c=o;if(u.has(c)){S[c]=i;continue}n[c]!==i&&(n[c]=i,t=!0)}t&&(s=n,f=null,y())});var N={get(e){return s[e]},set(e,n){b(e,n)&&v()},setRuntime(e,n){u.has(e)||(u.add(e),a.includes(e)&&(S[e]=s[e])),b(e,n)&&y()},isRuntimeControlled(e){return u.has(e)},getAll(){return s},apply(e){let n=!1,t={...s};for(let[o,i]of Object.entries(e)){if(i===void 0)continue;let c=o,r=l[c];r&&(r.validate&&!r.validate(i)||r.options&&!r.options.includes(i)||t[c]!==i&&(t[c]=i,n=!0))}n&&(s=t,f=null,v())},applyRuntime(e){let n=!1;for(let t of Object.keys(e)){let o=e[t];o!==void 0&&(u.has(t)||(u.add(t),a.includes(t)&&(S[t]=s[t])),b(t,o)&&(n=!0))}n&&y()},reset(){s=w(),f=null,v()},subscribe(e){return h.push(e),()=>{h=h.filter(n=>n!==e)}},getDeviceSpec(){return f||(f=k(s.deviceModel)),f},getA11yMode(){return s.a11yMode},setA11yMode(e){this.set("a11yMode",e)},getInspectMode(){return s.inspectMode},setInspectMode(e){this.set("inspectMode",e)}};export{N as a};
@@ -1,5 +0,0 @@
1
- /*! sootsim v0.1.291 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as C,b as D}from"./chunk-VK3IISND.js";import{H as E}from"./chunk-5NGZRMTN.js";var N="sootsimConfig";function p(e){return!!e&&Object.keys(e).length>0}function $(e){return e?p(e.modules)||p(e.turboModules)||p(e.nativeModules)||p(e.env)||p(e.network?.remap)||p(e.settings)||p(e.initialState):!1}function A(e,n){let t=new URL(e);return $(n)?t.searchParams.set(N,JSON.stringify(n)):t.searchParams.delete(N),t.toString()}var pe="/index.bundle?platform=ios&dev=true&hot=true&minify=false";function j(e){return/^https?:\/\//i.test(e)}function O(e){return e.endsWith(".bundle")}function H(e){process.env.SOOTSIM_PREVIEW_PROD_BUNDLE&&(e.searchParams.set("dev","false"),e.searchParams.set("minify","true"),e.searchParams.has("hot")&&e.searchParams.set("hot","false"))}function I(e){try{let n=j(e),t=new URL(e,"http://sootsim.local");return t.pathname=t.pathname.replace(/\.\.bundle$/,".bundle"),O(t.pathname)?(t.searchParams.delete("transform.bytecode"),t.searchParams.get("lazy")==="true"&&t.searchParams.set("lazy","false"),H(t),n?t.toString():`${t.pathname}${t.search}${t.hash}`):e}catch{return e}}function ge(e,n){try{let t=j(e),o=new URL(e,"http://sootsim.local");return O(o.pathname)?(o.searchParams.set("hot",n?"true":"false"),t?o.toString():`${o.pathname}${o.search}${o.hash}`):e}catch{return e}}import{exec as B}from"child_process";import W from"http";import F from"net";import{promisify as G}from"util";var T=G(B);C();var z=250,J=1500,K=120;function V(e,n=K){return new Promise(t=>{let o=new F.Socket,s=!1,i=a=>{s||(s=!0,o.destroy(),t(a))};o.setTimeout(n),o.once("connect",()=>i(!0)),o.once("timeout",()=>i(!1)),o.once("error",()=>i(!1)),o.connect(e,"localhost")})}function h(e,n,t="GET",o=z,s={}){return new Promise(i=>{let a=W.request({hostname:"localhost",port:e,path:n,method:t,timeout:o,headers:s},r=>{let c="";r.on("data",l=>c+=l.toString());let u=(()=>{let l=r.headers["content-type"];if(typeof l=="string")return l;if(Array.isArray(l))return l[0]})();r.on("end",()=>i({statusCode:r.statusCode||0,body:c,contentType:u}))});a.on("error",()=>i(null)),a.setTimeout(o,()=>{a.destroy(),i(null)}),a.end()})}var q=[8081,8082,8083,8084,8085,8086,3e3,3001,19e3].map(e=>({port:e,pid:0}));function k(e,n){return!(e<=0||e>=2e4||n.has(e)||e>=5170&&e<=5200)}async function Y(e=[]){let n=new Set(e);try{let{stdout:t}=await T("lsof -iTCP -sTCP:LISTEN -P -n 2>/dev/null | grep -E '^(node|bun)'",{encoding:"utf8",timeout:2e3});if(t.trim()){let o=new Map;for(let s of t.trim().split(`
3
- `)){let i=s.trim().split(/\s+/);if(i.length<9)continue;let a=Number(i[1]),c=i[8].match(/:(\d+)$/);if(!c)continue;let u=Number(c[1]);k(u,n)&&(o.has(u)||o.set(u,a))}if(o.size>0)return[...o.entries()].map(([s,i])=>({port:s,pid:i}))}}catch{}try{let{stdout:t}=await T(`ss -tlnp 2>/dev/null | grep -E '"(node|bun)"'`,{encoding:"utf8",timeout:2e3});if(t.trim()){let o=new Map;for(let s of t.trim().split(`
4
- `)){let i=s.match(/:(\d+)\s/),a=s.match(/pid=(\d+)/);if(!i)continue;let r=Number(i[1]),c=a?Number(a[1]):0;k(r,n)&&(o.has(r)||o.set(r,c))}if(o.size>0)return[...o.entries()].map(([s,i])=>({port:s,pid:i}))}}catch{}return q.filter(t=>k(t.port,n))}var x=new Map;async function Q(e){if(e<=0)return null;let n=x.get(e);if(n)return n;try{let{stdout:t}=await T(`lsof -p ${e} -a -d cwd -Fn 2>/dev/null`,{encoding:"utf8",timeout:1500});for(let o of t.split(`
5
- `))if(o.startsWith("n")&&o.length>1){let s=o.slice(1).trim();if(s)return x.set(e,s),s}}catch{}return null}function X(e,n){return{port:e,framework:n,bundleUrl:S(e,`http://localhost:${e}/index.bundle?platform=ios&dev=true&hot=true&minify=false`),hmrUrl:`ws://localhost:${e}/hot`,lastSeen:Date.now()}}function S(e,n){let t=D().find(s=>s.preferredPort===e),o=t?.runtimeConfig?A(n,t.runtimeConfig):n;return I(o)}function Z(e){return e.includes("/node_modules/one/metro-entry.bundle")}function ee(e){try{let n=JSON.parse(e);return n&&typeof n=="object"?n:null}catch{return null}}function w(e,n,t){if(!n)return e;try{let o=JSON.parse(n.body),s=o?.extra?.expoClient||o?.extra||{};s.name&&(e.projectName=s.name),s.ios?.bundleIdentifier&&(e.bundleId=s.ios.bundleIdentifier),e.framework==="metro"&&s.sdkVersion&&(e.framework="expo");let i=o?.launchAsset?.url;i&&!e.patched&&!Z(e.bundleUrl)&&(e.bundleUrl=S(e.port,i));let a=s.iconUrl||s.ios?.iconUrl||s.icon||s.ios?.icon;if(a)if(e.iconPath=a,t)if(a.startsWith("http"))e.iconUrl=t(a);else{let r=a.replace(/^\.\//,"");e.iconUrl=t(`http://localhost:${e.port}/assets/${r}`)}else e.iconUrl=a.startsWith("http")?a:`http://localhost:${e.port}/assets/${a.replace(/^\.\//,"")}`}catch{}return e}var ne=2e4,v=new Map,M=new Set;function _(e,n){if(e.projectName)return e;let t=v.get(e.port);if(t)return t.projectName&&(e.projectName=t.projectName),t.iconUrl&&!e.iconUrl&&(e.iconUrl=t.iconUrl,e.iconPath=t.iconPath),t.bundleId&&!e.bundleId&&(e.bundleId=t.bundleId),e;if(!M.has(e.port)){M.add(e.port);let o=e.port;te(o,n).finally(()=>M.delete(o))}return e}async function te(e,n){let t=await h(e,"/","GET",ne,{"expo-platform":"ios"});if(!t||t.statusCode!==200)return;let o=w({port:e,framework:"one",bundleUrl:"",lastSeen:0},t,n);!o.projectName&&!o.iconUrl||(v.set(e,{projectName:o.projectName,iconUrl:o.iconUrl,iconPath:o.iconPath,bundleId:o.bundleId}),b.delete(e))}var d=new Set,g=new Set,y=new Set;async function oe(e,n){if(!await V(e))return null;let t="/node_modules/one/metro-entry.bundle?platform=ios&dev=true",[o,s,i,a]=await Promise.all([d.has(e)?Promise.resolve(null):h(e,"/__soot/"),h(e,"/status"),y.has(e)?Promise.resolve(null):h(e,"/","GET",J,{"expo-platform":"ios"}),g.has(e)?Promise.resolve(null):h(e,"/_expo/status")]);a&&a.statusCode===200?g.delete(e):g.has(e)||g.add(e);let r=i?ee(i.body):null,c=typeof r?.launchAsset?.url=="string"?r.launchAsset.url:null,u=r?.extra?.expoClient||r?.extra||{};if(r&&(c||typeof u.name=="string")){d.add(e);let U=c||`http://localhost:${e}/index.bundle?platform=ios&dev=true&hot=true&minify=false`,L=U.includes("/one/metro-entry.bundle")?"one":"expo";return w({port:e,framework:L,bundleUrl:S(e,U),hmrUrl:`ws://localhost:${e}/hot`,lastSeen:Date.now()},i,n)}if(s&&s.body.includes("packager-status:running"))return d.add(e),_(w(X(e,a&&a.statusCode===200?"expo":"metro"),i,n),n);if(o&&o.statusCode===200&&o.body.includes("sootsim-patched"))return d.delete(e),_(w({port:e,framework:"one",bundleUrl:S(e,`http://localhost:${e}/__soot/bundle.js`),hmrUrl:`ws://localhost:${e}/hot`,lastSeen:Date.now(),patched:!0},i,n),n);if(s&&s.statusCode===200||a&&a.statusCode===200||i&&i.statusCode===200&&r!=null)return null;let m=await h(e,t,"HEAD");return m&&m.statusCode>0&&m.statusCode<400&&/application\/javascript/i.test(m.contentType||"")?(d.add(e),y.add(e),_(w({port:e,framework:"one",bundleUrl:S(e,`http://localhost:${e}${t}&minify=false`),hmrUrl:`ws://localhost:${e}/hot`,lastSeen:Date.now()},i,n),n)):(d.add(e),null)}function re(e){return!!e.bundleId?.trim().toLowerCase()?.startsWith("dev.sootsim.")}var b=new Map,se=3e4,ie=1500;function ae(e){return!e||e.framework==="metro"||e.framework==="unknown"}function ce(e){return e?S(e.port,e.bundleUrl)===e.bundleUrl:!0}function ue(e,n,t=Date.now()){if(n===0||e.pid!==n||!ce(e.result))return!1;let o=t-e.cachedAt;return!(e.result===null&&o>=se||ae(e.result)&&o>=ie)}async function Pe(e={}){await C();let n=await Y(e.excludePorts),t=new Set(n.map(r=>r.port));for(let r of[...b.keys()])t.has(r)||b.delete(r);for(let r of[...d])t.has(r)||d.delete(r);for(let r of[...g])t.has(r)||g.delete(r);for(let r of[...y])t.has(r)||y.delete(r);for(let r of[...v.keys()])t.has(r)||v.delete(r);let o=[],s=[];for(let{port:r,pid:c}of n){let u=b.get(r);if(u&&ue(u,c)){u.result&&o.push(u.result);continue}u&&u.pid!==c&&(d.delete(r),g.delete(r),y.delete(r),v.delete(r)),s.push({port:r,pid:c})}s.length>0&&(await Promise.all(s.map(c=>oe(c.port,e.buildIconProxyUrl)))).forEach((c,u)=>{let{port:l,pid:m}=s[u];m!==0&&b.set(l,{pid:m,result:c,cachedAt:Date.now()}),c&&o.push(c)});let i=new Map;for(let{port:r,pid:c}of n)c>0&&i.set(r,c);await Promise.all(o.map(async r=>{let c=i.get(r.port);if(!c)return;r.pid=c;let u=await Q(c);u&&(r.cwd=u)}));let a=new Set(i.values());for(let r of[...x.keys()])a.has(r)||x.delete(r);return o.filter(r=>!re(r))}function le(e){return e.replace(/^\[|\]$/g,"").toLowerCase()}function ke(e){let n=le(e);return n==="localhost"||n.endsWith(".localhost")||n==="0.0.0.0"||n==="::1"||/^127(?:\.\d{1,3}){3}$/.test(n)}import R from"fs";import f from"path";import{fileURLToPath as de}from"url";var P;function fe(){if(P!==void 0)return P;let e=f.dirname(de(import.meta.url));for(let n=0;n<12&&e!==f.dirname(e);n++){if(R.existsSync(f.join(e,"packages/sootsim-engine/package.json"))&&R.existsSync(f.join(e,"public/sootsim")))return P=e,e;e=f.dirname(e)}return P=null,null}function Ne(){if(process.env.SOOTSIM_HOME&&E())return null;let e=fe();if(!e)return null;let n=f.join(e,"public/sootsim");return R.existsSync(f.join(n,"index.html"))&&R.existsSync(f.join(n,"engine/index.html"))&&R.existsSync(f.join(n,"engine-tenant/worker.js"))?n:null}export{A as a,pe as b,I as c,ge as d,oe as e,Pe as f,ke as g,Ne as h};