rnxsim 0.1.313 → 0.1.314
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 +11 -10
- package/README.md +5 -0
- package/cli/app-config.ts +65 -0
- package/cli/app-fonts.ts +408 -0
- package/cli/app-project.ts +231 -0
- package/cli/app-splash.ts +185 -0
- package/cli/app-state-reset.ts +24 -0
- package/cli/auth.ts +155 -0
- package/cli/bin.ts +594 -0
- package/cli/bridge-diagnostics.ts +226 -0
- package/cli/bridge-flow-runner.ts +2830 -0
- package/cli/browser-evals.ts +96 -0
- package/cli/commands/agent-wrapper.ts +986 -0
- package/cli/commands/agent.ts +423 -0
- package/cli/commands/app-fonts.ts +98 -0
- package/cli/commands/assert.ts +541 -0
- package/cli/commands/auth.ts +59 -0
- package/cli/commands/camera.ts +266 -0
- package/cli/commands/cleanup.ts +169 -0
- package/cli/commands/compat.ts +87 -0
- package/cli/commands/config.ts +32 -0
- package/cli/commands/control.ts +2142 -0
- package/cli/commands/cpu-profile.ts +269 -0
- package/cli/commands/daemon-mac-app.ts +169 -0
- package/cli/commands/daemon.ts +874 -0
- package/cli/commands/debug.ts +719 -0
- package/cli/commands/desktop.ts +39 -0
- package/cli/commands/detect.ts +197 -0
- package/cli/commands/detox.ts +385 -0
- package/cli/commands/device.ts +133 -0
- package/cli/commands/diagnose.ts +589 -0
- package/cli/commands/electron.ts +95 -0
- package/cli/commands/film.ts +379 -0
- package/cli/commands/flow.ts +1124 -0
- package/cli/commands/inspect/actions.ts +622 -0
- package/cli/commands/inspect/core.ts +2405 -0
- package/cli/commands/inspect/count.ts +17 -0
- package/cli/commands/inspect/describe.ts +192 -0
- package/cli/commands/inspect/env.ts +23 -0
- package/cli/commands/inspect/find.ts +171 -0
- package/cli/commands/inspect/get-layout.ts +39 -0
- package/cli/commands/inspect/keyboard.ts +52 -0
- package/cli/commands/inspect/list.ts +58 -0
- package/cli/commands/inspect/memory.ts +215 -0
- package/cli/commands/inspect/redaction.ts +39 -0
- package/cli/commands/inspect/resolve-target.ts +82 -0
- package/cli/commands/inspect/screens.ts +78 -0
- package/cli/commands/inspect/settle.ts +22 -0
- package/cli/commands/inspect/settling.ts +158 -0
- package/cli/commands/inspect/shared.ts +353 -0
- package/cli/commands/inspect/sleep.ts +14 -0
- package/cli/commands/inspect/tree.ts +32 -0
- package/cli/commands/inspect/url.ts +17 -0
- package/cli/commands/inspect/wait-event.ts +210 -0
- package/cli/commands/inspect/wait-idle.ts +24 -0
- package/cli/commands/inspect/wait-ready.ts +74 -0
- package/cli/commands/inspect/wait-selector.ts +54 -0
- package/cli/commands/inspect/wait.ts +31 -0
- package/cli/commands/inspect.ts +4519 -0
- package/cli/commands/install-desktop.ts +351 -0
- package/cli/commands/login.ts +331 -0
- package/cli/commands/logout.ts +31 -0
- package/cli/commands/maestro-generate.ts +361 -0
- package/cli/commands/maestro.ts +453 -0
- package/cli/commands/mode.ts +57 -0
- package/cli/commands/no-bridge-hint.ts +80 -0
- package/cli/commands/perf.ts +66 -0
- package/cli/commands/permissions.ts +203 -0
- package/cli/commands/profile.ts +108 -0
- package/cli/commands/react.ts +353 -0
- package/cli/commands/record.ts +1434 -0
- package/cli/commands/report-issue.ts +305 -0
- package/cli/commands/reset.ts +85 -0
- package/cli/commands/runtime.ts +351 -0
- package/cli/commands/screenshot-command.ts +106 -0
- package/cli/commands/screenshot-layers.ts +143 -0
- package/cli/commands/screenshot-mode.ts +37 -0
- package/cli/commands/screenshot.ts +488 -0
- package/cli/commands/screenshots-capture.ts +607 -0
- package/cli/commands/screenshots.ts +127 -0
- package/cli/commands/serve.ts +168 -0
- package/cli/commands/setup.ts +545 -0
- package/cli/commands/shell-boolean-mode.ts +81 -0
- package/cli/commands/skills.ts +467 -0
- package/cli/commands/slides.ts +361 -0
- package/cli/commands/state.ts +87 -0
- package/cli/commands/storage.ts +58 -0
- package/cli/commands/telemetry.ts +54 -0
- package/cli/commands/three-mode.ts +763 -0
- package/cli/commands/timeline.ts +122 -0
- package/cli/commands/upgrade.ts +208 -0
- package/cli/commands/upload.ts +1225 -0
- package/cli/commands/version.ts +54 -0
- package/cli/commands/what-happened.ts +327 -0
- package/cli/current-sim.ts +204 -0
- package/cli/desktop-companion.ts +300 -0
- package/cli/drivers/electron.ts +70 -0
- package/cli/drivers/index.ts +20 -0
- package/cli/drivers/playwright-provisioning.ts +180 -0
- package/cli/drivers/playwright.ts +698 -0
- package/cli/drivers/registry.ts +65 -0
- package/cli/drivers/types.ts +102 -0
- package/cli/flow-file.ts +142 -0
- package/cli/flow-live-status.ts +120 -0
- package/cli/flow-session.ts +187 -0
- package/cli/help.ts +80 -0
- package/cli/hidden-runtime-alias.ts +19 -0
- package/cli/hints.ts +216 -0
- package/cli/inspect-notice-state.ts +114 -0
- package/cli/maestro-js.ts +334 -0
- package/cli/open-url.ts +8 -0
- package/cli/parent-pid.ts +204 -0
- package/cli/parse-args.ts +211 -0
- package/cli/prompt.ts +51 -0
- package/cli/recording-access.ts +107 -0
- package/cli/registry.ts +1 -0
- package/cli/resolve-assets.ts +63 -0
- package/cli/run-registry.ts +226 -0
- package/cli/runtime-notes.ts +66 -0
- package/cli/runtime-summary.ts +25 -0
- package/cli/setup-repository.ts +187 -0
- package/cli/telemetry.ts +187 -0
- package/cli/ws-bridge.ts +798 -0
- package/dist-cli/bin.js +5 -5
- package/dist-cli/chunks/{agent-XZ2KTPCU.js → agent-7YBDCYMA.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-JJYYW2WH.js → agent-wrapper-2GHFBHCR.js} +2 -2
- package/dist-cli/chunks/{app-fonts-IXRNQG6B.js → app-fonts-RSNVPQSU.js} +2 -2
- package/dist-cli/chunks/{assert-54T5SK5F.js → assert-XCMX3XJX.js} +2 -2
- package/dist-cli/chunks/{auth-FI5UDI45.js → auth-B442HRAX.js} +2 -2
- package/dist-cli/chunks/{beta-JV6UKADW.js → beta-XJ55JK3M.js} +2 -2
- package/dist-cli/chunks/camera-UGYSSLIK.js +33 -0
- package/dist-cli/chunks/{chunk-3NV2NCNX.js → chunk-277AEQZX.js} +2 -2
- package/dist-cli/chunks/{chunk-2YR5BGA5.js → chunk-2JNSK774.js} +2 -2
- package/dist-cli/chunks/{chunk-RSZWCKNT.js → chunk-32WOTSTR.js} +3 -3
- package/dist-cli/chunks/{chunk-WEXDAC74.js → chunk-3S753SNQ.js} +2 -2
- package/dist-cli/chunks/{chunk-IJO63TDP.js → chunk-46ZOLOYA.js} +2 -2
- package/dist-cli/chunks/{chunk-WUSWBCWA.js → chunk-5L7ELDQL.js} +8 -9
- package/dist-cli/chunks/{chunk-WF3T4SVI.js → chunk-7OOPFSQS.js} +2 -2
- package/dist-cli/chunks/{chunk-TZFFR3SD.js → chunk-7SV3RPRW.js} +2 -2
- package/dist-cli/chunks/{chunk-WWZIXIRD.js → chunk-7ZC35MOU.js} +1 -1
- package/dist-cli/chunks/chunk-AMG5E6CC.js +9 -0
- package/dist-cli/chunks/{chunk-WINYQ44O.js → chunk-APWNH3A4.js} +1 -1
- package/dist-cli/chunks/chunk-B57XUKY3.js +4 -0
- package/dist-cli/chunks/{chunk-YDGQTMQL.js → chunk-C2NL26TD.js} +1 -1
- package/dist-cli/chunks/{chunk-NMF2ZMZQ.js → chunk-D2FNUWAB.js} +4 -4
- package/dist-cli/chunks/{chunk-YIFT42WN.js → chunk-E5T4XSJ3.js} +2 -2
- package/dist-cli/chunks/{chunk-46EUUFJ5.js → chunk-EAC34EQS.js} +1 -1
- package/dist-cli/chunks/{chunk-OVFJFXUD.js → chunk-EG32ML36.js} +2 -2
- package/dist-cli/chunks/{chunk-5YJCOWCH.js → chunk-FXUAC6D5.js} +1 -1
- package/dist-cli/chunks/chunk-GQSL4USA.js +6 -0
- package/dist-cli/chunks/{chunk-VFCMSYZK.js → chunk-HAXW27SS.js} +2 -2
- package/dist-cli/chunks/{chunk-2D2UPBBR.js → chunk-IZAHPAN6.js} +1 -1
- package/dist-cli/chunks/{chunk-7GN3LVWB.js → chunk-J62KM5TB.js} +2 -2
- package/dist-cli/chunks/{chunk-BTWORNNG.js → chunk-JEMCD5E4.js} +1 -1
- package/dist-cli/chunks/{chunk-DCEMHR2Y.js → chunk-JZS3Q37N.js} +2 -2
- package/dist-cli/chunks/{chunk-D4FFVGI5.js → chunk-KR4JON7D.js} +1 -1
- package/dist-cli/chunks/chunk-KWCYKQIQ.js +4 -0
- package/dist-cli/chunks/{chunk-VNQEB4L7.js → chunk-MCWPL644.js} +2 -2
- package/dist-cli/chunks/chunk-MJYK3N2I.js +4 -0
- package/dist-cli/chunks/{chunk-5TEF3ET3.js → chunk-O6TRIZNS.js} +2 -2
- package/dist-cli/chunks/{chunk-QKDWYITG.js → chunk-P7XL2E73.js} +3 -3
- package/dist-cli/chunks/{chunk-BBULZ7CG.js → chunk-PFQTUKQ4.js} +62 -87
- package/dist-cli/chunks/{chunk-GGRX24GF.js → chunk-PG5RZCTN.js} +2 -2
- package/dist-cli/chunks/{chunk-W6K4EFPH.js → chunk-QGRI2Z4M.js} +2 -2
- package/dist-cli/chunks/{chunk-ZMJD5GEC.js → chunk-QLXXE7GE.js} +1 -1
- package/dist-cli/chunks/{chunk-VZXWHRUZ.js → chunk-QOJJJWJE.js} +89 -133
- package/dist-cli/chunks/{chunk-OZSSI4WN.js → chunk-RWZY5427.js} +2 -2
- package/dist-cli/chunks/{chunk-UC6U3MML.js → chunk-RZKU2K3J.js} +2 -2
- package/dist-cli/chunks/{chunk-IJ5CAZZC.js → chunk-SBV4IK4H.js} +1 -1
- package/dist-cli/chunks/{chunk-DZS6WPUI.js → chunk-SGMVFFMK.js} +1 -1
- package/dist-cli/chunks/{chunk-OHAZNXLK.js → chunk-TAX4UT2N.js} +1 -1
- package/dist-cli/chunks/{chunk-5DHC6KHQ.js → chunk-TUOFAWXT.js} +1 -1
- package/dist-cli/chunks/{chunk-GASE6UBA.js → chunk-UHZLOHGP.js} +1 -1
- package/dist-cli/chunks/{chunk-F5ZRSS3C.js → chunk-VC7V76U3.js} +1 -1
- package/dist-cli/chunks/{chunk-RTN5C5RL.js → chunk-VQVMLW4U.js} +1 -1
- package/dist-cli/chunks/{chunk-WMIIKMGK.js → chunk-VUKKYPZN.js} +2 -2
- package/dist-cli/chunks/{chunk-5TPRP5QT.js → chunk-WGGRJDRE.js} +1 -1
- package/dist-cli/chunks/chunk-X6H76EKP.js +15 -0
- package/dist-cli/chunks/chunk-XLZ5FNRT.js +27 -0
- package/dist-cli/chunks/{chunk-HI5TFJWN.js → chunk-XULEACM4.js} +2 -2
- package/dist-cli/chunks/{chunk-MJRLLB4R.js → chunk-YFSDM7AX.js} +4 -4
- package/dist-cli/chunks/chunk-YWI3UEVX.js +5 -0
- package/dist-cli/chunks/{chunk-XEVZYVIW.js → chunk-ZBSJO4NB.js} +10 -9
- package/dist-cli/chunks/{cleanup-P27PA6JI.js → cleanup-EYLGCA6Z.js} +2 -2
- package/dist-cli/chunks/cli-version-LL2UGIHE.js +4 -0
- package/dist-cli/chunks/{compat-ZD65FED3.js → compat-TLJYHB4E.js} +2 -2
- package/dist-cli/chunks/{config-XMJRNM2A.js → config-4JWUOEXK.js} +2 -2
- package/dist-cli/chunks/{control-KMIQT3QP.js → control-HPAOYF4N.js} +2 -2
- package/dist-cli/chunks/daemon-OYLASXLE.js +4 -0
- package/dist-cli/chunks/{debug-PT4HOP7N.js → debug-4BKXF6KI.js} +5 -5
- package/dist-cli/chunks/{desktop-S3FG72AK.js → desktop-ZQ6ZD2S6.js} +3 -3
- package/dist-cli/chunks/{detox-B3D4IFCN.js → detox-WNPASSS3.js} +2 -2
- package/dist-cli/chunks/{device-XBNDSB2R.js → device-BXLXVG3V.js} +2 -2
- package/dist-cli/chunks/{diagnose-HMQXJE5N.js → diagnose-3QX7W5RW.js} +2 -2
- package/dist-cli/chunks/{disk-cleanup-BLCZ5BSZ.js → disk-cleanup-KSWKI7WB.js} +2 -2
- package/dist-cli/chunks/drivers-EPIEFF7P.js +4 -0
- package/dist-cli/chunks/{film-BJGTBYZB.js → film-GDMR33VO.js} +3 -3
- package/dist-cli/chunks/flow-NKMPBYCJ.js +4 -0
- package/dist-cli/chunks/help-OGCBHOKA.js +4 -0
- package/dist-cli/chunks/{hidden-runtime-alias-ANOYADHM.js → hidden-runtime-alias-S2GTDX3T.js} +2 -2
- package/dist-cli/chunks/home-paths-QRCDLTTV.js +4 -0
- package/dist-cli/chunks/inspect-FUYMOZPY.js +4 -0
- package/dist-cli/chunks/install-desktop-FR6YKW7Y.js +4 -0
- package/dist-cli/chunks/{login-FJ737MWG.js → login-IJAPUZHI.js} +4 -4
- package/dist-cli/chunks/{logout-ZCNMMHMY.js → logout-QMXDFU2X.js} +2 -2
- package/dist-cli/chunks/{maestro-SZTNKLDF.js → maestro-ZXU3YCVX.js} +3 -3
- package/dist-cli/chunks/{maestro-generate-DCFAIZ4H.js → maestro-generate-PYB5QY7K.js} +3 -3
- package/dist-cli/chunks/{mode-GRMQCRXR.js → mode-WGUCL5FZ.js} +2 -2
- package/dist-cli/chunks/{optional-demo-registry-W36EWFFB.js → optional-demo-registry-WH2O6H36.js} +2 -2
- package/dist-cli/chunks/{perf-QYBAAUZG.js → perf-TOD3UFAH.js} +2 -2
- package/dist-cli/chunks/{permissions-3QCQ6VF4.js → permissions-I5BRJGTB.js} +2 -2
- package/dist-cli/chunks/{record-QPPC2S4E.js → record-ZYL2FYSK.js} +3 -3
- package/dist-cli/chunks/{report-issue-7NMFP4HK.js → report-issue-TAI6DYZO.js} +2 -2
- package/dist-cli/chunks/reset-7YGYKQPQ.js +4 -0
- package/dist-cli/chunks/runtime-B4JO6QRI.js +4 -0
- package/dist-cli/chunks/{screenshot-command-67AECJFB.js → screenshot-command-7ANLODZY.js} +7 -7
- package/dist-cli/chunks/{screenshot-layers-ASWBYPJL.js → screenshot-layers-A7FYXSVU.js} +3 -3
- package/dist-cli/chunks/{screenshots-capture-PXA3HFQK.js → screenshots-capture-WT2ZY6CB.js} +2 -2
- package/dist-cli/chunks/serve-TG5WKA4V.js +44 -0
- package/dist-cli/chunks/{setup-7DWPMRSB.js → setup-PCWLD22V.js} +2 -2
- package/dist-cli/chunks/{skills-S3Y22TUA.js → skills-27ZHWCQS.js} +2 -2
- package/dist-cli/chunks/state-ZQVY46ZO.js +14 -0
- package/dist-cli/chunks/{storage-XUIMJWAJ.js → storage-OYC57C4X.js} +6 -6
- package/dist-cli/chunks/store-32HSPZHI.js +4 -0
- package/dist-cli/chunks/telemetry-PBJR7XHR.js +4 -0
- package/dist-cli/chunks/{timeline-TMPLQPSP.js → timeline-RAJJFQT6.js} +2 -2
- package/dist-cli/chunks/{upgrade-7HDSIM7K.js → upgrade-OEFNZRIP.js} +2 -2
- package/dist-cli/chunks/upload-LQJITLKK.js +4 -0
- package/dist-cli/chunks/version-NIXTY6PL.js +6 -0
- package/dist-cli/chunks/{web-DG3WBYD3.js → web-XBUTBVGR.js} +2 -2
- package/dist-cli/chunks/{what-happened-XFVUTZR7.js → what-happened-YNHRFUDX.js} +3 -3
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract.cjs +20 -0
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +67 -28
- package/dist-lib/host/bridge-host.cjs +140 -12
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +2815 -40
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +2549 -2061
- package/dist-lib/sdk.mjs +2543 -2061
- package/dist-lib/skills.cjs +480 -280
- package/dist-lib/vite.cjs +1 -1
- package/package.json +8 -2
- package/src/bridge-constants.ts +3 -4
- package/src/bridge-contract.ts +251 -0
- package/src/connect.ts +83 -0
- package/src/disk-cleanup.ts +30 -0
- package/src/home-paths.ts +81 -38
- package/src/host/bridge-host.ts +134 -6
- package/src/index.ts +27 -1
- package/src/sdk.ts +8 -0
- package/src/sim-client.ts +660 -0
- package/dist-cli/chunks/camera-VL73YIKP.js +0 -22
- package/dist-cli/chunks/chunk-4NPPOV2N.js +0 -5
- package/dist-cli/chunks/chunk-FSUYIVJ6.js +0 -9
- package/dist-cli/chunks/chunk-G2WW6L2C.js +0 -23
- package/dist-cli/chunks/chunk-KTHV3RUS.js +0 -26
- package/dist-cli/chunks/chunk-LF2ZVT7O.js +0 -6
- package/dist-cli/chunks/chunk-NFK7T35W.js +0 -4
- package/dist-cli/chunks/chunk-TIVZIMMW.js +0 -4
- package/dist-cli/chunks/cli-version-WWLPBDQ7.js +0 -4
- package/dist-cli/chunks/daemon-G2ME7NLB.js +0 -4
- package/dist-cli/chunks/drivers-LDECZGP2.js +0 -4
- package/dist-cli/chunks/flow-UEQNVTU7.js +0 -4
- package/dist-cli/chunks/help-T5FYSVGB.js +0 -4
- package/dist-cli/chunks/home-paths-GT3LFNOR.js +0 -4
- package/dist-cli/chunks/inspect-ZA6XF5LD.js +0 -4
- package/dist-cli/chunks/install-desktop-TIMUDHPL.js +0 -4
- package/dist-cli/chunks/runtime-XOAXMSTU.js +0 -4
- package/dist-cli/chunks/serve-BI2NBAXG.js +0 -44
- package/dist-cli/chunks/store-JTHEJLAZ.js +0 -4
- package/dist-cli/chunks/telemetry-ZYJGD2DB.js +0 -4
- package/dist-cli/chunks/upload-GMSZPWM6.js +0 -4
- package/dist-cli/chunks/version-HOCHZ37L.js +0 -6
package/LICENSE
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
Proprietary Software License
|
|
2
2
|
|
|
3
|
-
Copyright (c) Tamagui LLC
|
|
3
|
+
Copyright (c) Tamagui LLC. All rights reserved.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
5
|
+
This package (rnxsim) is proprietary software of Tamagui LLC, licensed under
|
|
6
|
+
the rnx Software License Agreement: https://rnxsim.com/legal/license
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
In summary (the agreement governs): personal, non-commercial use is free;
|
|
9
|
+
commercial use requires an active Pro subscription after a single 30-day
|
|
10
|
+
evaluation period; embedding or redistributing the software requires a
|
|
11
|
+
separate written agreement with Tamagui LLC.
|
|
12
|
+
|
|
13
|
+
Versions of this package published before this license change remain under
|
|
14
|
+
the license they were published with.
|
|
14
15
|
|
|
15
16
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
17
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
package/README.md
CHANGED
|
@@ -157,6 +157,11 @@ guest-app fetch/WebSocket traffic so the cross-origin tenant worker can reach
|
|
|
157
157
|
localhost. The `AgentHost` extension owns one FIFO reader per agent session and
|
|
158
158
|
fans agent events out to every subscriber.
|
|
159
159
|
|
|
160
|
+
During source development, each Vite bridge owns one
|
|
161
|
+
`~/.sootsim/dev-bridges/<port>.json` heartbeat record. CLI defaults select the
|
|
162
|
+
current `PORT_OFFSET`, then the current checkout, then the canonical `:7668`
|
|
163
|
+
bridge, so concurrent worktrees cannot replace or delete one another's address.
|
|
164
|
+
|
|
160
165
|
**The sim** is a browser/Electron *page* launched by a CLI driver. Inside that
|
|
161
166
|
page run three workers: the **shell worker** owns device and native state, the
|
|
162
167
|
**compositor worker** paints every visible CanvasKit surface, and the **tenant
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { isValidRuntimeVersion } from '@contrast/runtime-delivery'
|
|
3
|
+
import { createJiti } from 'jiti'
|
|
4
|
+
import { rnxPublicBrand } from '../src/public-brand'
|
|
5
|
+
import { resolveAppProject, type ResolvedAppProject } from './app-project'
|
|
6
|
+
import type { SootSimConfig } from '../src/config'
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url)
|
|
9
|
+
|
|
10
|
+
function isOptionalRecord(value: unknown): boolean {
|
|
11
|
+
return (
|
|
12
|
+
value === undefined ||
|
|
13
|
+
(value !== null && typeof value === 'object' && !Array.isArray(value))
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isSootSimConfig(value: unknown): value is SootSimConfig {
|
|
18
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false
|
|
19
|
+
const runtimeVersion = Reflect.get(value, 'runtimeVersion')
|
|
20
|
+
const network = Reflect.get(value, 'network')
|
|
21
|
+
return (
|
|
22
|
+
(runtimeVersion === undefined ||
|
|
23
|
+
(typeof runtimeVersion === 'string' &&
|
|
24
|
+
runtimeVersion === runtimeVersion.trim() &&
|
|
25
|
+
isValidRuntimeVersion(runtimeVersion))) &&
|
|
26
|
+
isOptionalRecord(Reflect.get(value, 'modules')) &&
|
|
27
|
+
isOptionalRecord(Reflect.get(value, 'turboModules')) &&
|
|
28
|
+
isOptionalRecord(Reflect.get(value, 'nativeModules')) &&
|
|
29
|
+
isOptionalRecord(Reflect.get(value, 'env')) &&
|
|
30
|
+
isOptionalRecord(network) &&
|
|
31
|
+
isOptionalRecord(Reflect.get(value, 'settings')) &&
|
|
32
|
+
isOptionalRecord(Reflect.get(value, 'initialState'))
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function loadSootSimAppConfig(
|
|
37
|
+
project: ResolvedAppProject | null,
|
|
38
|
+
): Promise<SootSimConfig | undefined> {
|
|
39
|
+
if (!project?.configPath) return undefined
|
|
40
|
+
try {
|
|
41
|
+
const jiti = createJiti(import.meta.url, {
|
|
42
|
+
interopDefault: true,
|
|
43
|
+
alias: {
|
|
44
|
+
[`${rnxPublicBrand.packageName}/config`]: require.resolve(
|
|
45
|
+
`${rnxPublicBrand.packageName}/config`,
|
|
46
|
+
),
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
const loaded: unknown = await jiti.import(project.configPath, { default: true })
|
|
50
|
+
if (!isSootSimConfig(loaded)) {
|
|
51
|
+
throw new Error('the default export must be an rnx config object')
|
|
52
|
+
}
|
|
53
|
+
return loaded
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
56
|
+
throw new Error(`could not load ${project.configPath}: ${message}`)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function resolveSootSimAppConfig(
|
|
61
|
+
startDir: string = process.cwd(),
|
|
62
|
+
): Promise<{ project: ResolvedAppProject | null; config: SootSimConfig | undefined }> {
|
|
63
|
+
const project = resolveAppProject(startDir)
|
|
64
|
+
return { project, config: await loadSootSimAppConfig(project) }
|
|
65
|
+
}
|
package/cli/app-fonts.ts
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import {
|
|
6
|
+
expoFontPathsFromPlugins,
|
|
7
|
+
metroFontSpecsFromPaths,
|
|
8
|
+
type ConfigPluginFontSpec,
|
|
9
|
+
} from 'sootsim-engine/engine/config-plugin-fonts'
|
|
10
|
+
import { resolveListeningPid, resolveProcessCwd } from '../scripts/dev-server-scanner'
|
|
11
|
+
import { parsePlistSource } from '../src/plist.ts'
|
|
12
|
+
import { resolveAppProject } from './app-project'
|
|
13
|
+
|
|
14
|
+
const require = createRequire(import.meta.url)
|
|
15
|
+
|
|
16
|
+
// the filesystem scan and the engine's metro-manifest scan share the same spec
|
|
17
|
+
// shape and the same path → spec logic (config-plugin-fonts.ts).
|
|
18
|
+
export type DiscoveredAppFontSpec = ConfigPluginFontSpec
|
|
19
|
+
|
|
20
|
+
type ExpoConfigGetter = (dir: string, opts: Record<string, unknown>) => { exp?: unknown }
|
|
21
|
+
|
|
22
|
+
function localOriginOf(value: string): string | null {
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL(value)
|
|
25
|
+
const host = url.hostname.replace(/^\[|\]$/g, '').toLowerCase()
|
|
26
|
+
if (
|
|
27
|
+
host === 'localhost' ||
|
|
28
|
+
host.endsWith('.localhost') ||
|
|
29
|
+
host === '0.0.0.0' ||
|
|
30
|
+
host === '::1' ||
|
|
31
|
+
/^127(?:\.\d{1,3}){3}$/.test(host)
|
|
32
|
+
) {
|
|
33
|
+
return url.origin
|
|
34
|
+
}
|
|
35
|
+
} catch {}
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hasPackageJson(dir: string): boolean {
|
|
40
|
+
try {
|
|
41
|
+
return fs.statSync(path.join(dir, 'package.json')).isFile()
|
|
42
|
+
} catch {
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function discoverNativeLinkedAppFonts(opts: {
|
|
47
|
+
bundleUrl: string
|
|
48
|
+
repoDir?: string
|
|
49
|
+
exactProjectDir?: boolean
|
|
50
|
+
platform?: string
|
|
51
|
+
}): DiscoveredAppFontSpec[] {
|
|
52
|
+
const metroOrigin = localOriginOf(opts.bundleUrl)
|
|
53
|
+
if (!metroOrigin) return []
|
|
54
|
+
|
|
55
|
+
const explicit = opts.repoDir || process.env.CONTRAST_REPO_DIR
|
|
56
|
+
const startDir = explicit ? path.resolve(explicit) : process.cwd()
|
|
57
|
+
|
|
58
|
+
// if we're using cwd() (no explicit repo) and there's no package.json,
|
|
59
|
+
// assume we're not in a project tree — bail immediately instead of
|
|
60
|
+
// recursing the filesystem.
|
|
61
|
+
if (!explicit && !hasPackageJson(startDir)) return []
|
|
62
|
+
|
|
63
|
+
const project = resolveAppProject(startDir, { trustStart: opts.exactProjectDir })
|
|
64
|
+
const selectedAppDir = project?.appDir ?? null
|
|
65
|
+
if (!selectedAppDir) return []
|
|
66
|
+
|
|
67
|
+
const platform = (opts.platform || process.env.CONTRAST_PLATFORM || 'ios').toLowerCase()
|
|
68
|
+
|
|
69
|
+
let getConfig: ExpoConfigGetter | null = null
|
|
70
|
+
// use the app config dirs + startDir as resolve paths. real repos hoist
|
|
71
|
+
// @expo/config to the root node_modules so startDir is sufficient; tests
|
|
72
|
+
// that mock @expo/config can set CONTRAST_REPO_DIR to point where the mock lives.
|
|
73
|
+
if (explicit || project) {
|
|
74
|
+
try {
|
|
75
|
+
const modulePath = require.resolve('@expo/config', {
|
|
76
|
+
paths: [selectedAppDir, startDir],
|
|
77
|
+
})
|
|
78
|
+
const mod = require(modulePath) as { getConfig?: unknown }
|
|
79
|
+
if (typeof mod.getConfig === 'function') {
|
|
80
|
+
getConfig = mod.getConfig as ExpoConfigGetter
|
|
81
|
+
}
|
|
82
|
+
} catch {}
|
|
83
|
+
}
|
|
84
|
+
let plugins: unknown = []
|
|
85
|
+
if (getConfig) {
|
|
86
|
+
try {
|
|
87
|
+
const result = getConfig(selectedAppDir, { skipSDKVersionRequirement: true })
|
|
88
|
+
if (result?.exp && typeof result.exp === 'object') {
|
|
89
|
+
const resolvedPlugins = (result.exp as Record<string, unknown>).plugins
|
|
90
|
+
if (Array.isArray(resolvedPlugins)) {
|
|
91
|
+
plugins = resolvedPlugins
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch {}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const fontPaths = expoFontPathsFromPlugins(
|
|
98
|
+
plugins,
|
|
99
|
+
platform === 'android' ? 'android' : 'ios',
|
|
100
|
+
).slice(0, 256)
|
|
101
|
+
// the walk is bounded by WORK, not by a clock: at most 64 readdirs, at most 64
|
|
102
|
+
// queued dirs, 4 levels deep, 256 paths. those ceilings are what keep a stray
|
|
103
|
+
// node_modules from being crawled, and no machine can blow through them. a
|
|
104
|
+
// wall-clock budget on top of that only makes the RESULT depend on how busy
|
|
105
|
+
// the box is, and silently — a user on a loaded machine would get fewer fonts
|
|
106
|
+
// and no error at all.
|
|
107
|
+
let scannedFontDirectories = 0
|
|
108
|
+
|
|
109
|
+
const collectFontFiles = (root: string, metroPathPrefix: string): void => {
|
|
110
|
+
const pending: Array<{ dir: string; depth: number }> = [{ dir: root, depth: 0 }]
|
|
111
|
+
let nextIndex = 0
|
|
112
|
+
while (
|
|
113
|
+
nextIndex < pending.length &&
|
|
114
|
+
scannedFontDirectories < 64 &&
|
|
115
|
+
fontPaths.length < 256
|
|
116
|
+
) {
|
|
117
|
+
const current = pending[nextIndex++]
|
|
118
|
+
if (!current) continue
|
|
119
|
+
scannedFontDirectories++
|
|
120
|
+
let entries: fs.Dirent[]
|
|
121
|
+
try {
|
|
122
|
+
entries = fs.readdirSync(current.dir, { withFileTypes: true })
|
|
123
|
+
} catch {
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
for (const entry of entries) {
|
|
127
|
+
if (entry.isSymbolicLink()) continue
|
|
128
|
+
const fullPath = path.join(current.dir, entry.name)
|
|
129
|
+
if (entry.isDirectory()) {
|
|
130
|
+
if (current.depth < 4 && pending.length < 64 && !entry.name.startsWith('.')) {
|
|
131
|
+
pending.push({ dir: fullPath, depth: current.depth + 1 })
|
|
132
|
+
}
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
if (!entry.isFile() || !/\.(ttf|otf|woff|woff2)$/i.test(entry.name)) continue
|
|
136
|
+
const relative = path.relative(root, fullPath).split(path.sep).join('/')
|
|
137
|
+
fontPaths.push(path.posix.join(metroPathPrefix, relative))
|
|
138
|
+
if (fontPaths.length >= 256) break
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// @react-native-vector-icons links fonts from its configured local font
|
|
144
|
+
// directory and each installed icon package into the native app. those files
|
|
145
|
+
// never enter the metro module graph, so SootSim must stage them through the
|
|
146
|
+
// same native-linked font wire as expo-font config-plugin assets. Font Awesome
|
|
147
|
+
// Pro installs its licensed faces under the local directory rather than
|
|
148
|
+
// publishing them in the npm package.
|
|
149
|
+
try {
|
|
150
|
+
const packageJson: object = JSON.parse(
|
|
151
|
+
fs.readFileSync(path.join(selectedAppDir, 'package.json'), 'utf8'),
|
|
152
|
+
)
|
|
153
|
+
const dependencyNames = new Set<string>()
|
|
154
|
+
for (const field of ['dependencies', 'devDependencies', 'optionalDependencies']) {
|
|
155
|
+
const dependencies = Reflect.get(packageJson, field)
|
|
156
|
+
if (!dependencies || typeof dependencies !== 'object') continue
|
|
157
|
+
for (const name of Object.keys(dependencies)) dependencyNames.add(name)
|
|
158
|
+
}
|
|
159
|
+
const vectorIconDependencies = [...dependencyNames].filter(
|
|
160
|
+
(name) =>
|
|
161
|
+
name === 'react-native-vector-icons' ||
|
|
162
|
+
name.startsWith('@react-native-vector-icons/'),
|
|
163
|
+
)
|
|
164
|
+
if (vectorIconDependencies.length > 0) {
|
|
165
|
+
const config = Reflect.get(packageJson, 'reactNativeVectorIcons')
|
|
166
|
+
const configuredFontDir =
|
|
167
|
+
config && typeof config === 'object' ? Reflect.get(config, 'fontDir') : null
|
|
168
|
+
const configuredDir =
|
|
169
|
+
typeof configuredFontDir === 'string' ? configuredFontDir : 'rnvi-fonts'
|
|
170
|
+
const root = path.resolve(selectedAppDir, configuredDir)
|
|
171
|
+
const relativeRoot = path.relative(selectedAppDir, root)
|
|
172
|
+
if (!relativeRoot.startsWith('..') && !path.isAbsolute(relativeRoot)) {
|
|
173
|
+
collectFontFiles(root, relativeRoot.split(path.sep).join('/'))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const vectorIconRequire = createRequire(path.join(selectedAppDir, 'package.json'))
|
|
177
|
+
for (const dependencyName of vectorIconDependencies) {
|
|
178
|
+
try {
|
|
179
|
+
const entryPath = vectorIconRequire.resolve(dependencyName)
|
|
180
|
+
const dependencyPath = dependencyName.split('/').join(path.sep)
|
|
181
|
+
const marker = `${path.sep}node_modules${path.sep}${dependencyPath}${path.sep}`
|
|
182
|
+
const markerIndex = entryPath.lastIndexOf(marker)
|
|
183
|
+
if (markerIndex === -1) continue
|
|
184
|
+
const packageRoot = entryPath.slice(0, markerIndex + marker.length - 1)
|
|
185
|
+
const fontsDir =
|
|
186
|
+
dependencyName === 'react-native-vector-icons' ? 'Fonts' : 'fonts'
|
|
187
|
+
collectFontFiles(
|
|
188
|
+
path.join(packageRoot, fontsDir),
|
|
189
|
+
path.posix.join('node_modules', dependencyName, fontsDir),
|
|
190
|
+
)
|
|
191
|
+
} catch {}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} catch {}
|
|
195
|
+
|
|
196
|
+
// react-native.config.js `assets` is the RN CLI's native asset-linking
|
|
197
|
+
// source: `npx react-native-asset` copies every font in those directories
|
|
198
|
+
// into the app bundle and Info.plist UIAppFonts, and the JS never requires
|
|
199
|
+
// the files, so they enter no module graph. apps like mattermost register
|
|
200
|
+
// icon sets (createIconSetFromFontello) purely by family name against a
|
|
201
|
+
// font linked this way.
|
|
202
|
+
try {
|
|
203
|
+
const configPath = path.join(selectedAppDir, 'react-native.config.js')
|
|
204
|
+
if (fs.statSync(configPath).isFile()) {
|
|
205
|
+
const appConfigRequire = createRequire(path.join(selectedAppDir, 'package.json'))
|
|
206
|
+
const rnConfig: unknown = appConfigRequire(configPath)
|
|
207
|
+
const assets =
|
|
208
|
+
rnConfig && typeof rnConfig === 'object' ? Reflect.get(rnConfig, 'assets') : null
|
|
209
|
+
if (Array.isArray(assets)) {
|
|
210
|
+
for (const assetDir of assets) {
|
|
211
|
+
if (typeof assetDir !== 'string') continue
|
|
212
|
+
const root = path.resolve(selectedAppDir, assetDir)
|
|
213
|
+
const relativeRoot = path.relative(selectedAppDir, root)
|
|
214
|
+
if (relativeRoot.startsWith('..') || path.isAbsolute(relativeRoot)) continue
|
|
215
|
+
collectFontFiles(root, relativeRoot.split(path.sep).join('/'))
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} catch {}
|
|
220
|
+
|
|
221
|
+
// bare iOS projects can embed fonts directly in an Xcode target without a
|
|
222
|
+
// react-native.config.js assets declaration. UIAppFonts is the native
|
|
223
|
+
// contract, so stage those referenced files through Metro's asset endpoint.
|
|
224
|
+
if (platform === 'ios') {
|
|
225
|
+
const iosDir = path.join(selectedAppDir, 'ios')
|
|
226
|
+
try {
|
|
227
|
+
const targetDirs = fs
|
|
228
|
+
.readdirSync(iosDir, { withFileTypes: true })
|
|
229
|
+
.filter(
|
|
230
|
+
(entry) =>
|
|
231
|
+
entry.isDirectory() &&
|
|
232
|
+
!entry.isSymbolicLink() &&
|
|
233
|
+
entry.name !== 'Pods' &&
|
|
234
|
+
entry.name !== 'build' &&
|
|
235
|
+
!entry.name.startsWith('.'),
|
|
236
|
+
)
|
|
237
|
+
.slice(0, 64)
|
|
238
|
+
const plistPaths = [path.join(iosDir, 'Info.plist')]
|
|
239
|
+
for (const entry of targetDirs) {
|
|
240
|
+
plistPaths.push(path.join(iosDir, entry.name, 'Info.plist'))
|
|
241
|
+
}
|
|
242
|
+
for (const plistPath of plistPaths) {
|
|
243
|
+
let parsed: unknown
|
|
244
|
+
try {
|
|
245
|
+
parsed = parsePlistSource(fs.readFileSync(plistPath, 'utf8'))
|
|
246
|
+
} catch {
|
|
247
|
+
continue
|
|
248
|
+
}
|
|
249
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) continue
|
|
250
|
+
const declaredFonts = Reflect.get(parsed, 'UIAppFonts')
|
|
251
|
+
if (!Array.isArray(declaredFonts)) continue
|
|
252
|
+
for (const declaredFont of declaredFonts) {
|
|
253
|
+
if (typeof declaredFont !== 'string') continue
|
|
254
|
+
const candidates = [
|
|
255
|
+
path.resolve(path.dirname(plistPath), declaredFont),
|
|
256
|
+
path.resolve(iosDir, declaredFont),
|
|
257
|
+
]
|
|
258
|
+
const fontPath = candidates.find((candidate) => {
|
|
259
|
+
const relative = path.relative(selectedAppDir, candidate)
|
|
260
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) return false
|
|
261
|
+
try {
|
|
262
|
+
return fs.statSync(candidate).isFile()
|
|
263
|
+
} catch {
|
|
264
|
+
return false
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
if (!fontPath) continue
|
|
268
|
+
fontPaths.push(
|
|
269
|
+
path.relative(selectedAppDir, fontPath).split(path.sep).join('/'),
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} catch {}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return metroFontSpecsFromPaths(fontPaths, metroOrigin)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// resolve a metro dev server's project root. expo serves a manifest at the
|
|
280
|
+
// server origin carrying `extra.expoClient._internal.projectRoot` — the absolute
|
|
281
|
+
// dir of the app being served. the local `sootsim open <port>` path runs from the
|
|
282
|
+
// CLI's cwd, which almost never owns the bundle being opened (e.g.
|
|
283
|
+
// `sootsim open 8081` run from ~/soot against a 3pc metro), so scanning cwd for
|
|
284
|
+
// font declarations finds nothing.
|
|
285
|
+
//
|
|
286
|
+
// a bare react-native packager serves no such manifest — it answers `/` with an
|
|
287
|
+
// HTML page — so fall back to asking the OS which directory the listening
|
|
288
|
+
// process runs from. bare apps are exactly the ones that link fonts through
|
|
289
|
+
// react-native.config.js `assets`, so without this their fonts are never
|
|
290
|
+
// discoverable and the appFonts wire comes out empty.
|
|
291
|
+
export async function resolveMetroProjectRoot(
|
|
292
|
+
bundleUrl: string,
|
|
293
|
+
opts?: { timeoutMs?: number },
|
|
294
|
+
): Promise<string | null> {
|
|
295
|
+
const origin = localOriginOf(bundleUrl)
|
|
296
|
+
if (!origin) return null
|
|
297
|
+
const controller = new AbortController()
|
|
298
|
+
const timer = setTimeout(() => controller.abort(), opts?.timeoutMs ?? 1500)
|
|
299
|
+
let servedManifest = false
|
|
300
|
+
try {
|
|
301
|
+
const res = await fetch(`${origin}/`, {
|
|
302
|
+
headers: { 'expo-platform': 'ios', Accept: 'application/json' },
|
|
303
|
+
signal: controller.signal,
|
|
304
|
+
})
|
|
305
|
+
if (res.ok) {
|
|
306
|
+
const manifest = (await res.json()) as {
|
|
307
|
+
extra?: { expoClient?: { _internal?: { projectRoot?: unknown } } }
|
|
308
|
+
}
|
|
309
|
+
servedManifest = true
|
|
310
|
+
const root = manifest?.extra?.expoClient?._internal?.projectRoot
|
|
311
|
+
if (typeof root === 'string' && root.length > 0) return root
|
|
312
|
+
}
|
|
313
|
+
} catch {
|
|
314
|
+
} finally {
|
|
315
|
+
clearTimeout(timer)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// a server that answered with a manifest has already told us everything it
|
|
319
|
+
// knows. if it did not name a project root there is none to learn, and asking
|
|
320
|
+
// the OS about the listening process would answer with whatever directory
|
|
321
|
+
// that process happens to sit in — which is the CLI's own cwd trap this
|
|
322
|
+
// function exists to avoid. only a server that served no manifest at all gets
|
|
323
|
+
// the process lookup.
|
|
324
|
+
if (servedManifest) return null
|
|
325
|
+
|
|
326
|
+
const port = Number(new URL(origin).port)
|
|
327
|
+
if (!Number.isFinite(port) || port <= 0) return null
|
|
328
|
+
const pid = await resolveListeningPid(port)
|
|
329
|
+
return pid === null ? null : resolveProcessCwd(pid)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function encodeAppFontsWire(specs: DiscoveredAppFontSpec[]): string {
|
|
333
|
+
return specs
|
|
334
|
+
.map((spec) => `${encodeURIComponent(spec.url)}::${encodeURIComponent(spec.family)}`)
|
|
335
|
+
.join(',')
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export function parseAppFontUrls(wire: string): string[] {
|
|
339
|
+
const urls: string[] = []
|
|
340
|
+
for (const entry of wire.split(',')) {
|
|
341
|
+
const trimmed = entry.trim()
|
|
342
|
+
if (!trimmed) continue
|
|
343
|
+
const sep = trimmed.indexOf('::')
|
|
344
|
+
const urlPart = sep === -1 ? trimmed : trimmed.slice(0, sep)
|
|
345
|
+
try {
|
|
346
|
+
urls.push(decodeURIComponent(urlPart))
|
|
347
|
+
} catch {
|
|
348
|
+
urls.push(urlPart)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return urls.filter(Boolean)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// content type for a config-plugin font url, by extension. config fonts only
|
|
355
|
+
// ever ship as ttf/otf/woff(2); fall back to octet-stream for anything else
|
|
356
|
+
// so R2 still serves bytes the engine can register.
|
|
357
|
+
export function fontContentType(url: string): string {
|
|
358
|
+
const ext = (url.split('?')[0].split('.').pop() || '').toLowerCase()
|
|
359
|
+
if (ext === 'ttf') return 'font/ttf'
|
|
360
|
+
if (ext === 'otf') return 'font/otf'
|
|
361
|
+
if (ext === 'woff') return 'font/woff'
|
|
362
|
+
if (ext === 'woff2') return 'font/woff2'
|
|
363
|
+
return 'application/octet-stream'
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export type FetchedAppFont = {
|
|
367
|
+
url: string
|
|
368
|
+
// sha256(url) — the share-store key both the build and preview upload paths
|
|
369
|
+
// use, and what /api/preview/share/fetch?url=<fontUrl> resolves against.
|
|
370
|
+
urlhash: string
|
|
371
|
+
contentType: string
|
|
372
|
+
bytes: Uint8Array
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// fetch every config-plugin font declared on an engine `?appFonts=` wire.
|
|
376
|
+
// these typefaces live in the native binary, never in the metro bundle's
|
|
377
|
+
// __packager_asset graph, so neither asset capture nor recorded fetches see
|
|
378
|
+
// them — both the preview (`sootsim record upload`) and build (`sootsim app-fonts
|
|
379
|
+
// stage`) paths backfill by re-fetching the declared urls off the still-live
|
|
380
|
+
// metro. one implementation so both pipelines store identical bytes + keys.
|
|
381
|
+
export async function fetchAppFontFiles(
|
|
382
|
+
wire: string,
|
|
383
|
+
hooks?: {
|
|
384
|
+
onStaged?: (info: { url: string; byteLength: number }) => void
|
|
385
|
+
onError?: (url: string, err: unknown) => void
|
|
386
|
+
},
|
|
387
|
+
): Promise<FetchedAppFont[]> {
|
|
388
|
+
const out: FetchedAppFont[] = []
|
|
389
|
+
const seen = new Set<string>()
|
|
390
|
+
for (const url of parseAppFontUrls(wire)) {
|
|
391
|
+
const urlhash = createHash('sha256').update(url).digest('hex')
|
|
392
|
+
if (seen.has(urlhash)) continue
|
|
393
|
+
seen.add(urlhash)
|
|
394
|
+
try {
|
|
395
|
+
const res = await fetch(url)
|
|
396
|
+
if (!res.ok) {
|
|
397
|
+
hooks?.onError?.(url, new Error(`${res.status} ${res.statusText}`))
|
|
398
|
+
continue
|
|
399
|
+
}
|
|
400
|
+
const bytes = new Uint8Array(await res.arrayBuffer())
|
|
401
|
+
out.push({ url, urlhash, contentType: fontContentType(url), bytes })
|
|
402
|
+
hooks?.onStaged?.({ url, byteLength: bytes.byteLength })
|
|
403
|
+
} catch (err) {
|
|
404
|
+
hooks?.onError?.(url, err)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return out
|
|
408
|
+
}
|