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
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// sootsim perf cpu — capture a sampled CPU trace from the tenant worker.
|
|
2
|
+
//
|
|
3
|
+
// the JS Self-Profiler API (`new Profiler()`) cannot be constructed in a
|
|
4
|
+
// dedicated-worker scope, and sootsim runs the guest app in the tenant worker,
|
|
5
|
+
// so the old in-worker approach was unsupported (F28). this implementation
|
|
6
|
+
// attaches Chromium's `Profiler` CDP domain to the worker target instead —
|
|
7
|
+
// which works for workers and returns the .cpuprofile shape directly.
|
|
8
|
+
//
|
|
9
|
+
// dedicated workers are not top-level CDP targets, so we connect to the page
|
|
10
|
+
// target, `Target.setAutoAttach` to discover the worker child sessions, and
|
|
11
|
+
// route `Profiler.*` to each worker via its sessionId (flatten mode).
|
|
12
|
+
//
|
|
13
|
+
// the user's normal sootsim tab runs in plain Chrome without a debug port.
|
|
14
|
+
// the easiest way to get a profilable sim is to open one with a CDP port —
|
|
15
|
+
// it stays driveable over the WS bridge, so you can scroll/navigate while
|
|
16
|
+
// profiling (the playwright driver passes --remote-debugging-port through):
|
|
17
|
+
//
|
|
18
|
+
// sootsim open <metro-port> --new --driver playwright --cdp-port 9222
|
|
19
|
+
// sootsim perf cpu --cdp-port 9222 --match <metro-port> --duration 5
|
|
20
|
+
//
|
|
21
|
+
// (a standalone CDP Chrome also works for profiling a static page, but it
|
|
22
|
+
// does NOT register on the bridge, so it can't be driven — see printNoCdp.)
|
|
23
|
+
//
|
|
24
|
+
// interact with the app during the capture window. the reference probe that
|
|
25
|
+
// also drives the interaction itself lives at
|
|
26
|
+
// scripts/debug/cdp-worker-cpu-profile.ts.
|
|
27
|
+
|
|
28
|
+
import { mkdirSync, writeFileSync } from 'fs'
|
|
29
|
+
import { dirname, resolve } from 'path'
|
|
30
|
+
import { WebSocket } from 'ws'
|
|
31
|
+
|
|
32
|
+
interface ProfileOptions {
|
|
33
|
+
port?: number
|
|
34
|
+
verbose?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
|
38
|
+
|
|
39
|
+
// compact CDP client with flatten-mode sessionId routing — one ws to the page
|
|
40
|
+
// target carries the page session plus every auto-attached worker session.
|
|
41
|
+
class Cdp {
|
|
42
|
+
private ws: WebSocket
|
|
43
|
+
private nextId = 1
|
|
44
|
+
private pending = new Map<number, { res: (v: any) => void; rej: (e: any) => void }>()
|
|
45
|
+
private listeners = new Set<(method: string, params: any) => void>()
|
|
46
|
+
private ready: Promise<void>
|
|
47
|
+
|
|
48
|
+
constructor(wsUrl: string) {
|
|
49
|
+
this.ws = new WebSocket(wsUrl, { maxPayload: 1 << 28 })
|
|
50
|
+
this.ready = new Promise((res, rej) => {
|
|
51
|
+
this.ws.once('open', () => res())
|
|
52
|
+
this.ws.once('error', rej)
|
|
53
|
+
})
|
|
54
|
+
this.ws.on('message', (data) => {
|
|
55
|
+
const msg = JSON.parse(data.toString())
|
|
56
|
+
if (msg.id && this.pending.has(msg.id)) {
|
|
57
|
+
const { res, rej } = this.pending.get(msg.id)!
|
|
58
|
+
this.pending.delete(msg.id)
|
|
59
|
+
msg.error ? rej(new Error(JSON.stringify(msg.error))) : res(msg.result)
|
|
60
|
+
} else if (msg.method) {
|
|
61
|
+
for (const l of this.listeners) l(msg.method, msg.params)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
waitOpen() {
|
|
67
|
+
return this.ready
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
on(l: (method: string, params: any) => void) {
|
|
71
|
+
this.listeners.add(l)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
send(
|
|
75
|
+
method: string,
|
|
76
|
+
params: Record<string, unknown> = {},
|
|
77
|
+
sessionId?: string,
|
|
78
|
+
): Promise<any> {
|
|
79
|
+
const id = this.nextId++
|
|
80
|
+
return new Promise((res, rej) => {
|
|
81
|
+
this.pending.set(id, { res, rej })
|
|
82
|
+
this.ws.send(
|
|
83
|
+
JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) }),
|
|
84
|
+
)
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
close() {
|
|
89
|
+
try {
|
|
90
|
+
this.ws.close()
|
|
91
|
+
} catch {}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface CdpTarget {
|
|
96
|
+
type: string
|
|
97
|
+
url: string
|
|
98
|
+
webSocketDebuggerUrl: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function runCpuProfile(
|
|
102
|
+
args: string[],
|
|
103
|
+
opts: ProfileOptions,
|
|
104
|
+
): Promise<number> {
|
|
105
|
+
const duration = Number(valueOf(args, '--duration') ?? '5')
|
|
106
|
+
if (!Number.isFinite(duration) || duration <= 0) {
|
|
107
|
+
console.error(' --duration must be a positive number (seconds)')
|
|
108
|
+
return 1
|
|
109
|
+
}
|
|
110
|
+
const sampleInterval = Number(valueOf(args, '--sample-interval') ?? '0.1') // ms
|
|
111
|
+
const cdpPort = Number(
|
|
112
|
+
valueOf(args, '--cdp-port') ?? process.env.SOOTSIM_CDP_PORT ?? '9222',
|
|
113
|
+
)
|
|
114
|
+
// optional substring filter to pick the right page when several sims share
|
|
115
|
+
// the CDP browser (e.g. `--match 8089` or `--match /rn/8089`).
|
|
116
|
+
const match = valueOf(args, '--match') ?? '/rn/'
|
|
117
|
+
const outputArg = valueOf(args, '--output') ?? valueOf(args, '-o')
|
|
118
|
+
const outputPath = resolve(process.cwd(), outputArg ?? '/tmp/sootsim.cpuprofile')
|
|
119
|
+
|
|
120
|
+
// locate the page target on the CDP endpoint. NOTE: Chrome's DevTools HTTP
|
|
121
|
+
// `/json` endpoints reject any Host header that isn't `localhost` (a
|
|
122
|
+
// DNS-rebinding guard) — `http://127.0.0.1:<port>/json/list` returns
|
|
123
|
+
// "Not found" / 404, so we must use the `localhost` hostname here. (this is
|
|
124
|
+
// what made the command silently fall into the no-CDP path even with a live
|
|
125
|
+
// CDP browser.)
|
|
126
|
+
let targets: CdpTarget[] = []
|
|
127
|
+
try {
|
|
128
|
+
targets = (await (
|
|
129
|
+
await fetch(`http://localhost:${cdpPort}/json/list`)
|
|
130
|
+
).json()) as CdpTarget[]
|
|
131
|
+
} catch {
|
|
132
|
+
printNoCdp(cdpPort)
|
|
133
|
+
return 1
|
|
134
|
+
}
|
|
135
|
+
const page = targets.find((t) => t.type === 'page' && t.url.includes(match))
|
|
136
|
+
if (!page) {
|
|
137
|
+
console.error(` no page target matching "${match}" on CDP :${cdpPort}`)
|
|
138
|
+
console.error(` open targets: ${targets.map((t) => t.url).join(', ') || '(none)'}`)
|
|
139
|
+
return 1
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const cdp = new Cdp(page.webSocketDebuggerUrl)
|
|
143
|
+
await cdp.waitOpen()
|
|
144
|
+
try {
|
|
145
|
+
// discover worker child sessions via auto-attach.
|
|
146
|
+
const workers = new Map<string, string>() // sessionId -> url
|
|
147
|
+
cdp.on((method, params) => {
|
|
148
|
+
if (method === 'Target.attachedToTarget' && params.targetInfo?.type === 'worker') {
|
|
149
|
+
workers.set(params.sessionId, params.targetInfo.url)
|
|
150
|
+
}
|
|
151
|
+
if (method === 'Target.detachedFromTarget') workers.delete(params.sessionId)
|
|
152
|
+
})
|
|
153
|
+
await cdp.send('Target.setAutoAttach', {
|
|
154
|
+
autoAttach: true,
|
|
155
|
+
waitForDebuggerOnStart: false,
|
|
156
|
+
flatten: true,
|
|
157
|
+
})
|
|
158
|
+
// auto-attach events arrive asynchronously after the call resolves.
|
|
159
|
+
await sleep(500)
|
|
160
|
+
if (workers.size === 0) {
|
|
161
|
+
console.error(' no worker targets attached — is the app loaded in this tab?')
|
|
162
|
+
return 1
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (const sid of workers.keys()) {
|
|
166
|
+
await cdp.send('Profiler.enable', {}, sid)
|
|
167
|
+
await cdp.send(
|
|
168
|
+
'Profiler.setSamplingInterval',
|
|
169
|
+
{ interval: sampleInterval * 1000 },
|
|
170
|
+
sid,
|
|
171
|
+
) // µs
|
|
172
|
+
await cdp.send('Profiler.start', {}, sid)
|
|
173
|
+
}
|
|
174
|
+
console.log(
|
|
175
|
+
` recording ${duration}s across ${workers.size} worker(s) — interact now…`,
|
|
176
|
+
)
|
|
177
|
+
await sleep(duration * 1000)
|
|
178
|
+
|
|
179
|
+
const results: Array<{ url: string; profile: any }> = []
|
|
180
|
+
for (const [sid, url] of workers) {
|
|
181
|
+
const { profile } = await cdp.send('Profiler.stop', {}, sid)
|
|
182
|
+
results.push({ url, profile })
|
|
183
|
+
}
|
|
184
|
+
results.sort((a, b) => b.profile.samples.length - a.profile.samples.length)
|
|
185
|
+
|
|
186
|
+
mkdirSync(dirname(outputPath), { recursive: true })
|
|
187
|
+
// save the busiest worker (the tenant during interaction) to the main path;
|
|
188
|
+
// others get a suffix so a multi-worker capture isn't lost.
|
|
189
|
+
results.forEach((r, i) => {
|
|
190
|
+
const path =
|
|
191
|
+
i === 0 ? outputPath : outputPath.replace(/(\.[^.]+)?$/, `.worker${i}$1`)
|
|
192
|
+
writeFileSync(path, JSON.stringify(r.profile))
|
|
193
|
+
console.log(` ${shortUrl(r.url)}: ${r.profile.samples.length} samples → ${path}`)
|
|
194
|
+
if (opts.verbose && r.profile.samples.length > 0) {
|
|
195
|
+
for (const fn of topSelfTime(r.profile, 12)) {
|
|
196
|
+
console.log(` ${fn.pct.toFixed(1).padStart(5)}% ${fn.name} ${fn.url}`)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
console.log(' open in chrome devtools → Performance → Load profile to inspect.')
|
|
201
|
+
return 0
|
|
202
|
+
} finally {
|
|
203
|
+
cdp.close()
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function printNoCdp(cdpPort: number) {
|
|
208
|
+
console.error(` perf cpu needs a CDP-enabled browser on :${cdpPort}.`)
|
|
209
|
+
console.error(
|
|
210
|
+
" the tenant worker can't be profiled via the JS Self-Profiler API (F28),",
|
|
211
|
+
)
|
|
212
|
+
console.error(' so the sim must expose Chrome remote debugging. easiest path —')
|
|
213
|
+
console.error(' open the sim with a CDP port (it stays driveable over the bridge,')
|
|
214
|
+
console.error(' so you can scroll/navigate while profiling):')
|
|
215
|
+
console.error('')
|
|
216
|
+
console.error(
|
|
217
|
+
` rnxsim open <metro-port> --new --driver playwright --cdp-port ${cdpPort}`,
|
|
218
|
+
)
|
|
219
|
+
console.error(` rnxsim perf cpu --cdp-port ${cdpPort} --match <metro-port>`)
|
|
220
|
+
console.error('')
|
|
221
|
+
console.error(' or launch a standalone CDP Chrome (not bridge-driveable):')
|
|
222
|
+
console.error('')
|
|
223
|
+
console.error(' "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \\')
|
|
224
|
+
console.error(
|
|
225
|
+
` --remote-debugging-port=${cdpPort} --user-data-dir=/tmp/sootsim-cdp \\`,
|
|
226
|
+
)
|
|
227
|
+
console.error(' --no-sandbox --disable-gpu-sandbox \\')
|
|
228
|
+
// never suggest swiftshader — software rendering pegs every core for the
|
|
229
|
+
// browser's lifetime (documented machine-freeze cause). metal is the real
|
|
230
|
+
// rendering path anyway, so the profile is representative.
|
|
231
|
+
console.error(' --use-gl=angle --use-angle=metal \\')
|
|
232
|
+
console.error(' "http://localhost:5173/rn/<metro-port>" &')
|
|
233
|
+
console.error('')
|
|
234
|
+
console.error(' override the port with --cdp-port or $SOOTSIM_CDP_PORT.')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function shortUrl(url: string): string {
|
|
238
|
+
if (!url) return '(native)'
|
|
239
|
+
return url
|
|
240
|
+
.replace(/^https?:\/\/[^/]+/, '')
|
|
241
|
+
.replace(/\?.*$/, '')
|
|
242
|
+
.slice(-50)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function topSelfTime(profile: any, topN: number) {
|
|
246
|
+
const total = profile.samples.length || 1
|
|
247
|
+
const byFn = new Map<string, { self: number; name: string; url: string }>()
|
|
248
|
+
for (const node of profile.nodes) {
|
|
249
|
+
const f = node.callFrame
|
|
250
|
+
const key = `${f.functionName || '(anonymous)'}@${shortUrl(f.url)}:${f.lineNumber}`
|
|
251
|
+
const cur = byFn.get(key) ?? {
|
|
252
|
+
self: 0,
|
|
253
|
+
name: f.functionName || '(anonymous)',
|
|
254
|
+
url: `${shortUrl(f.url)}:${f.lineNumber}`,
|
|
255
|
+
}
|
|
256
|
+
cur.self += node.hitCount ?? 0
|
|
257
|
+
byFn.set(key, cur)
|
|
258
|
+
}
|
|
259
|
+
return [...byFn.values()]
|
|
260
|
+
.map((v) => ({ ...v, pct: (v.self / total) * 100 }))
|
|
261
|
+
.sort((a, b) => b.self - a.self)
|
|
262
|
+
.slice(0, topN)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function valueOf(args: string[], flag: string): string | undefined {
|
|
266
|
+
const i = args.indexOf(flag)
|
|
267
|
+
if (i < 0 || i === args.length - 1) return undefined
|
|
268
|
+
return args[i + 1]
|
|
269
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// generate + ad-hoc sign the SootSim Daemon.app wrapper that the launchd
|
|
2
|
+
// agent points its ProgramArguments at.
|
|
3
|
+
//
|
|
4
|
+
// why this exists: macOS Background Task Management (the "X wants to run in
|
|
5
|
+
// the background" prompt + the Login Items entry) reads the *signer* of
|
|
6
|
+
// ProgramArguments[0] to decide what name to display. when launchd is
|
|
7
|
+
// pointed at bun directly the entry shows "software from Jarred Sumner"
|
|
8
|
+
// because that's bun's code-signing identity. wrapping the real invocation
|
|
9
|
+
// in an ad-hoc-signed `.app` lets BTM read CFBundleDisplayName from the
|
|
10
|
+
// bundle Info.plist instead, so the entry reads "SootSim Daemon".
|
|
11
|
+
//
|
|
12
|
+
// the bundle is regenerated every time `daemonInstall` runs — it's tiny
|
|
13
|
+
// (a plist + a shell launcher) and the launcher embeds a snapshot of the
|
|
14
|
+
// invocation, so an `npm update -g sootsim` followed by `sootsim daemon
|
|
15
|
+
// install --force` is the documented upgrade path. there's no notarization
|
|
16
|
+
// or Developer ID signing here yet; ad-hoc + hardened runtime is enough to
|
|
17
|
+
// fix the attribution, and a real signed bundle (shipped via the runtime
|
|
18
|
+
// CDN) is a follow-up.
|
|
19
|
+
|
|
20
|
+
import { execFileSync } from 'child_process'
|
|
21
|
+
import { chmodSync, mkdirSync, rmSync, writeFileSync } from 'fs'
|
|
22
|
+
import { dirname } from 'path'
|
|
23
|
+
import {
|
|
24
|
+
daemonAppBundlePath,
|
|
25
|
+
daemonAppDir,
|
|
26
|
+
daemonAppLauncherPath,
|
|
27
|
+
} from '../../src/home-paths'
|
|
28
|
+
|
|
29
|
+
const BUNDLE_IDENTIFIER = 'dev.sootsim.daemon'
|
|
30
|
+
const BUNDLE_DISPLAY_NAME = 'SootSim Daemon'
|
|
31
|
+
// kept in sync with package.json on release. exact value doesn't matter for
|
|
32
|
+
// BTM display — macOS only reads it for "About this app" surfaces — so we
|
|
33
|
+
// hardcode it rather than reach back into the package.json from a runtime
|
|
34
|
+
// path that varies between npm-global, bun-compiled standalone, and dev
|
|
35
|
+
// checkouts.
|
|
36
|
+
const BUNDLE_VERSION = '0.1.36'
|
|
37
|
+
|
|
38
|
+
export interface DaemonInvocation {
|
|
39
|
+
/** absolute path to the executable launchd should spawn. */
|
|
40
|
+
executable: string
|
|
41
|
+
/** arguments passed to the executable (before server/--port flags). */
|
|
42
|
+
prefixArgs: string[]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface DaemonAppPaths {
|
|
46
|
+
/** the rebuilt .app bundle. */
|
|
47
|
+
bundlePath: string
|
|
48
|
+
/** the Mach-O / shell launcher inside the bundle. launchd points here. */
|
|
49
|
+
launcherPath: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** rebuild ~/.sootsim/daemon-app/SootSim Daemon.app/ for the given invocation
|
|
53
|
+
* and re-sign it ad-hoc. safe to call repeatedly; the directory is wiped
|
|
54
|
+
* before write so stale state from earlier installs can't leak through. */
|
|
55
|
+
export function ensureDaemonApp(
|
|
56
|
+
invocation: DaemonInvocation,
|
|
57
|
+
port: number,
|
|
58
|
+
logDir: { stdout: string; stderr: string },
|
|
59
|
+
): DaemonAppPaths {
|
|
60
|
+
const bundlePath = daemonAppBundlePath()
|
|
61
|
+
const launcherPath = daemonAppLauncherPath()
|
|
62
|
+
const contentsDir = dirname(dirname(launcherPath))
|
|
63
|
+
const macOSDir = dirname(launcherPath)
|
|
64
|
+
const resourcesDir = `${contentsDir}/Resources`
|
|
65
|
+
|
|
66
|
+
rmSync(bundlePath, { recursive: true, force: true })
|
|
67
|
+
mkdirSync(daemonAppDir(), { recursive: true })
|
|
68
|
+
mkdirSync(macOSDir, { recursive: true })
|
|
69
|
+
mkdirSync(resourcesDir, { recursive: true })
|
|
70
|
+
|
|
71
|
+
writeFileSync(`${contentsDir}/Info.plist`, renderInfoPlist())
|
|
72
|
+
writeFileSync(`${contentsDir}/PkgInfo`, 'APPL????')
|
|
73
|
+
writeFileSync(launcherPath, renderLauncherScript(invocation, port, logDir))
|
|
74
|
+
chmodSync(launcherPath, 0o755)
|
|
75
|
+
|
|
76
|
+
// ad-hoc sign the bundle so macOS treats it as a real .app and BTM reads
|
|
77
|
+
// its Info.plist. `--deep` covers the inner launcher script + future
|
|
78
|
+
// resources without us having to enumerate them. signing is best-effort —
|
|
79
|
+
// an unsigned bundle still works for launchd, it just falls back to the
|
|
80
|
+
// worse BTM display, which is no worse than the status quo.
|
|
81
|
+
try {
|
|
82
|
+
execFileSync('codesign', ['--force', '--sign', '-', '--deep', bundlePath], {
|
|
83
|
+
stdio: 'pipe',
|
|
84
|
+
})
|
|
85
|
+
} catch (err) {
|
|
86
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
87
|
+
console.warn(
|
|
88
|
+
` warning: ad-hoc codesign of ${bundlePath} failed: ${msg}\n` +
|
|
89
|
+
` the daemon will still run, but Login Items may show a generic name.`,
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { bundlePath, launcherPath }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function renderInfoPlist(): string {
|
|
97
|
+
// LSUIElement + LSBackgroundOnly together hide the app from the Dock and
|
|
98
|
+
// the Cmd-Tab switcher. CFBundleDisplayName is what Login Items shows.
|
|
99
|
+
// NSHumanReadableCopyright is what "About" surfaces show.
|
|
100
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
101
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
102
|
+
<plist version="1.0">
|
|
103
|
+
<dict>
|
|
104
|
+
<key>CFBundleDevelopmentRegion</key><string>en</string>
|
|
105
|
+
<key>CFBundleDisplayName</key><string>${BUNDLE_DISPLAY_NAME}</string>
|
|
106
|
+
<key>CFBundleExecutable</key><string>sootsim-daemon</string>
|
|
107
|
+
<key>CFBundleIdentifier</key><string>${BUNDLE_IDENTIFIER}</string>
|
|
108
|
+
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
|
|
109
|
+
<key>CFBundleName</key><string>${BUNDLE_DISPLAY_NAME}</string>
|
|
110
|
+
<key>CFBundlePackageType</key><string>APPL</string>
|
|
111
|
+
<key>CFBundleShortVersionString</key><string>${BUNDLE_VERSION}</string>
|
|
112
|
+
<key>CFBundleSignature</key><string>????</string>
|
|
113
|
+
<key>CFBundleVersion</key><string>${BUNDLE_VERSION}</string>
|
|
114
|
+
<key>LSBackgroundOnly</key><true/>
|
|
115
|
+
<key>LSUIElement</key><true/>
|
|
116
|
+
<key>LSMinimumSystemVersion</key><string>13.0</string>
|
|
117
|
+
<key>NSHumanReadableCopyright</key><string>Tamagui LLC</string>
|
|
118
|
+
</dict>
|
|
119
|
+
</plist>
|
|
120
|
+
`
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function renderLauncherScript(
|
|
124
|
+
invocation: DaemonInvocation,
|
|
125
|
+
port: number,
|
|
126
|
+
logDir: { stdout: string; stderr: string },
|
|
127
|
+
): string {
|
|
128
|
+
// `exec` replaces the shell process with bun/node, so launchd's PID
|
|
129
|
+
// tracking + KeepAlive both follow the real daemon process. stdout/stderr
|
|
130
|
+
// are already redirected by the launchd plist's StandardOutPath /
|
|
131
|
+
// StandardErrorPath, so we don't redirect again here — doing so would
|
|
132
|
+
// hide log output from `launchctl print`.
|
|
133
|
+
const parts = [
|
|
134
|
+
invocation.executable,
|
|
135
|
+
...invocation.prefixArgs,
|
|
136
|
+
'serve',
|
|
137
|
+
'--quiet',
|
|
138
|
+
'--port',
|
|
139
|
+
String(port),
|
|
140
|
+
]
|
|
141
|
+
const quoted = parts.map(shellSingleQuote).join(' ')
|
|
142
|
+
const stdout = shellSingleQuote(logDir.stdout)
|
|
143
|
+
const stderr = shellSingleQuote(logDir.stderr)
|
|
144
|
+
return `#!/bin/sh
|
|
145
|
+
# SootSim Daemon launcher — auto-generated by 'rnxsim setup'.
|
|
146
|
+
# launchd spawns this; we exec the real rnxsim invocation in place so
|
|
147
|
+
# launchd tracks the daemon process, not the shell wrapper.
|
|
148
|
+
#
|
|
149
|
+
# stdout: ${logDir.stdout}
|
|
150
|
+
# stderr: ${logDir.stderr}
|
|
151
|
+
# a missing or broken executable can make launchd retry forever. truncate a
|
|
152
|
+
# retry log before it passes 10 MiB so that failure stays bounded on disk.
|
|
153
|
+
for log_path in ${stdout} ${stderr}; do
|
|
154
|
+
if [ -f "$log_path" ]; then
|
|
155
|
+
log_bytes=$(wc -c < "$log_path" 2>/dev/null || echo 0)
|
|
156
|
+
if [ "$log_bytes" -gt 10485760 ]; then
|
|
157
|
+
: > "$log_path"
|
|
158
|
+
fi
|
|
159
|
+
fi
|
|
160
|
+
done
|
|
161
|
+
exec ${quoted}
|
|
162
|
+
`
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function shellSingleQuote(value: string): string {
|
|
166
|
+
// single-quote everything and escape embedded single quotes the POSIX way.
|
|
167
|
+
// safe for paths containing spaces, $, backticks, etc.
|
|
168
|
+
return `'${value.replace(/'/g, `'\\''`)}'`
|
|
169
|
+
}
|