peachsim 0.1.527
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +640 -0
- package/cli/app-config.ts +82 -0
- package/cli/app-fonts.ts +388 -0
- package/cli/app-project.ts +59 -0
- package/cli/app-splash.ts +171 -0
- package/cli/app-state-reset.ts +26 -0
- package/cli/auth.ts +184 -0
- package/cli/bin.ts +59 -0
- package/cli/bridge-diagnostics.ts +226 -0
- package/cli/bridge-flow-runner.ts +2806 -0
- package/cli/browser-backend.ts +44 -0
- package/cli/browser-evals.ts +96 -0
- package/cli/cli-update.ts +261 -0
- package/cli/cloud-client.ts +1530 -0
- package/cli/cloud-dispatch.ts +206 -0
- package/cli/cloud-session.ts +227 -0
- package/cli/command-contract.ts +221 -0
- package/cli/command-registry.ts +304 -0
- package/cli/commands/agent-wrapper.ts +1048 -0
- package/cli/commands/agent.ts +439 -0
- package/cli/commands/app-fonts.ts +99 -0
- package/cli/commands/assert.ts +542 -0
- package/cli/commands/auth.ts +71 -0
- package/cli/commands/box/bundle-plane.ts +71 -0
- package/cli/commands/box/checkout-plane.ts +256 -0
- package/cli/commands/box/plane-share.ts +208 -0
- package/cli/commands/box/rnx-command.ts +60 -0
- package/cli/commands/box/search-command.ts +157 -0
- package/cli/commands/box.ts +925 -0
- package/cli/commands/camera.ts +266 -0
- package/cli/commands/cleanup.ts +169 -0
- package/cli/commands/compat.ts +176 -0
- package/cli/commands/config.ts +34 -0
- package/cli/commands/control.ts +2154 -0
- package/cli/commands/cpu-profile.ts +941 -0
- package/cli/commands/daemon-mac-app.ts +165 -0
- package/cli/commands/daemon.ts +800 -0
- package/cli/commands/debug.ts +751 -0
- package/cli/commands/desktop.ts +39 -0
- package/cli/commands/detect.ts +201 -0
- package/cli/commands/detox.ts +481 -0
- package/cli/commands/device.ts +134 -0
- package/cli/commands/diagnose.ts +589 -0
- package/cli/commands/do-chain.ts +408 -0
- package/cli/commands/electron.ts +94 -0
- package/cli/commands/film.ts +379 -0
- package/cli/commands/flow.ts +1240 -0
- package/cli/commands/inspect/actions.ts +516 -0
- package/cli/commands/inspect/core.ts +2086 -0
- package/cli/commands/inspect/count.ts +17 -0
- package/cli/commands/inspect/describe.ts +119 -0
- package/cli/commands/inspect/env.ts +25 -0
- package/cli/commands/inspect/find.ts +169 -0
- package/cli/commands/inspect/get-layout.ts +53 -0
- package/cli/commands/inspect/keyboard.ts +54 -0
- package/cli/commands/inspect/list.ts +175 -0
- package/cli/commands/inspect/memory.ts +215 -0
- package/cli/commands/inspect/redaction.ts +39 -0
- package/cli/commands/inspect/resolve-target.ts +77 -0
- package/cli/commands/inspect/screens.ts +78 -0
- package/cli/commands/inspect/settle.ts +32 -0
- package/cli/commands/inspect/settling.ts +197 -0
- package/cli/commands/inspect/shared.ts +259 -0
- package/cli/commands/inspect/sleep.ts +15 -0
- package/cli/commands/inspect/tree.ts +23 -0
- package/cli/commands/inspect/url.ts +17 -0
- package/cli/commands/inspect/wait-event.ts +211 -0
- package/cli/commands/inspect/wait-idle.ts +25 -0
- package/cli/commands/inspect/wait-ready.ts +87 -0
- package/cli/commands/inspect/wait-selector.ts +59 -0
- package/cli/commands/inspect/wait.ts +32 -0
- package/cli/commands/inspect.ts +4738 -0
- package/cli/commands/install-cli.ts +275 -0
- package/cli/commands/install-desktop.ts +385 -0
- package/cli/commands/login.ts +357 -0
- package/cli/commands/logout.ts +24 -0
- package/cli/commands/maestro-generate.ts +375 -0
- package/cli/commands/maestro.ts +536 -0
- package/cli/commands/mode.ts +58 -0
- package/cli/commands/no-bridge-hint.ts +80 -0
- package/cli/commands/perf.ts +67 -0
- package/cli/commands/permissions.ts +203 -0
- package/cli/commands/platform.ts +410 -0
- package/cli/commands/preview.ts +103 -0
- package/cli/commands/profile.ts +106 -0
- package/cli/commands/react.ts +353 -0
- package/cli/commands/record.ts +1435 -0
- package/cli/commands/report-issue.ts +305 -0
- package/cli/commands/reset.ts +89 -0
- package/cli/commands/runtime.ts +348 -0
- package/cli/commands/screenshot-command.ts +106 -0
- package/cli/commands/screenshot-layers.ts +144 -0
- package/cli/commands/screenshot-mode.ts +38 -0
- package/cli/commands/screenshot.ts +492 -0
- package/cli/commands/screenshots-capture.ts +608 -0
- package/cli/commands/screenshots.ts +127 -0
- package/cli/commands/serve.ts +139 -0
- package/cli/commands/setup.ts +416 -0
- package/cli/commands/shell-boolean-mode.ts +82 -0
- package/cli/commands/skills.ts +468 -0
- package/cli/commands/slides.ts +357 -0
- package/cli/commands/state.ts +94 -0
- package/cli/commands/storage.ts +58 -0
- package/cli/commands/telemetry.ts +55 -0
- package/cli/commands/test.ts +535 -0
- package/cli/commands/three-mode.ts +771 -0
- package/cli/commands/timeline.ts +121 -0
- package/cli/commands/upgrade.ts +167 -0
- package/cli/commands/upload.ts +1203 -0
- package/cli/commands/version.ts +63 -0
- package/cli/commands/welcome.ts +17 -0
- package/cli/commands/what-happened.ts +328 -0
- package/cli/current-sim.ts +217 -0
- package/cli/desktop-companion.ts +315 -0
- package/cli/drivers/electron.ts +70 -0
- package/cli/drivers/index.ts +20 -0
- package/cli/drivers/playwright-provisioning.ts +184 -0
- package/cli/drivers/playwright-sim-host.ts +537 -0
- package/cli/drivers/playwright.ts +209 -0
- package/cli/drivers/registry.ts +65 -0
- package/cli/drivers/types.ts +102 -0
- package/cli/flow-export.ts +55 -0
- package/cli/flow-file.ts +142 -0
- package/cli/flow-live-status.ts +126 -0
- package/cli/flow-session.ts +140 -0
- package/cli/help-core.ts +143 -0
- package/cli/help.ts +44 -0
- package/cli/hidden-runtime-alias.ts +19 -0
- package/cli/hints.ts +214 -0
- package/cli/inspect-notice-state.ts +114 -0
- package/cli/internal-child.ts +176 -0
- package/cli/maestro-js.ts +323 -0
- package/cli/main.ts +704 -0
- package/cli/open-url.ts +8 -0
- package/cli/outbound-endpoints.ts +830 -0
- package/cli/parent-pid.ts +204 -0
- package/cli/parse-args.ts +189 -0
- package/cli/privacy.ts +53 -0
- package/cli/prompt.ts +56 -0
- package/cli/recording-access.ts +108 -0
- package/cli/registry.ts +2 -0
- package/cli/resolve-assets.ts +63 -0
- package/cli/rnx-in-process.ts +98 -0
- package/cli/run-registry.ts +226 -0
- package/cli/run-rnx-core.ts +148 -0
- package/cli/run-rnx.ts +137 -0
- package/cli/runtime-notes.ts +66 -0
- package/cli/runtime-summary.ts +25 -0
- package/cli/secure-file.ts +31 -0
- package/cli/self-invocation.ts +58 -0
- package/cli/send-to-box.ts +109 -0
- package/cli/setup-repository.ts +191 -0
- package/cli/shell-init.ts +98 -0
- package/cli/standalone.ts +4 -0
- package/cli/telemetry.ts +84 -0
- package/cli/ui.ts +68 -0
- package/cli/ws-bridge.ts +1020 -0
- package/detox/attempt-events.cjs +23 -0
- package/detox/colors.ts +54 -0
- package/detox/config-loader.ts +135 -0
- package/detox/element-types.ts +48 -0
- package/detox/expectations.ts +534 -0
- package/detox/gestures.ts +501 -0
- package/detox/index.ts +2694 -0
- package/detox/jest-preset.cjs +29 -0
- package/detox/jest-setup-after-env.cjs +22 -0
- package/detox/matchers.ts +30 -0
- package/detox/motion-change.cjs +109 -0
- package/detox/motion-change.d.cts +35 -0
- package/detox/navigation-lifecycle.ts +290 -0
- package/detox/navigation.ts +43 -0
- package/detox/proof-frame.cjs +114 -0
- package/detox/record-block-execution.cjs +78 -0
- package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
- package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
- package/detox/screenshots/color-test-rest-norngh.png +0 -0
- package/detox/screenshots/theme-blue-toggle.png +0 -0
- package/detox/screenshots/theme-blue.png +0 -0
- package/detox/screenshots/theme-red-toggle.png +0 -0
- package/detox/screenshots/theme-red.png +0 -0
- package/dist-lib/agent-daemon-client.cjs +502 -0
- package/dist-lib/agent-events.cjs +48 -0
- package/dist-lib/agent-identity.cjs +53 -0
- package/dist-lib/agent-sessions.cjs +759 -0
- package/dist-lib/attached-projects.cjs +526 -0
- package/dist-lib/auth/shared-session.cjs +208 -0
- package/dist-lib/backend-origin.cjs +88 -0
- package/dist-lib/beta.cjs +41 -0
- package/dist-lib/beta.mjs +13 -0
- package/dist-lib/bridge-constants.cjs +161 -0
- package/dist-lib/bridge-contract-input.cjs +451 -0
- package/dist-lib/bridge-contract-input.mjs +414 -0
- package/dist-lib/bridge-contract.cjs +130 -0
- package/dist-lib/bridge-contract.mjs +94 -0
- package/dist-lib/capture-contract.cjs +220 -0
- package/dist-lib/capture-contract.mjs +190 -0
- package/dist-lib/cli-constants.cjs +32 -0
- package/dist-lib/cloud-contract.cjs +194 -0
- package/dist-lib/cloud-contract.mjs +145 -0
- package/dist-lib/cloud.cjs +670 -0
- package/dist-lib/cloud.mjs +639 -0
- package/dist-lib/config.cjs +96 -0
- package/dist-lib/detox/index.cjs +3789 -0
- package/dist-lib/detox/jest-preset.cjs +29 -0
- package/dist-lib/dev-bundle-resolution.cjs +290 -0
- package/dist-lib/home-paths.cjs +638 -0
- package/dist-lib/host/bridge-host.cjs +10903 -0
- package/dist-lib/host/fetch-proxy-handler.cjs +421 -0
- package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
- package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
- package/dist-lib/host/replacement-module-handler.cjs +469 -0
- package/dist-lib/host/websocket-proxy.cjs +298 -0
- package/dist-lib/index.cjs +4268 -0
- package/dist-lib/jump-to-source-babel.cjs +93 -0
- package/dist-lib/jump-to-source-native.cjs +112 -0
- package/dist-lib/menu.cjs +646 -0
- package/dist-lib/menu.mjs +604 -0
- package/dist-lib/metro-fingerprint-registry.cjs +186 -0
- package/dist-lib/metro-fingerprint-registry.mjs +156 -0
- package/dist-lib/metro-production-bundle.cjs +329 -0
- package/dist-lib/metro-production-bundle.mjs +295 -0
- package/dist-lib/metro.cjs +904 -0
- package/dist-lib/profiles.cjs +246 -0
- package/dist-lib/public-brand.cjs +39 -0
- package/dist-lib/react-native-host-modules.cjs +51 -0
- package/dist-lib/react-native-host-modules.mjs +25 -0
- package/dist-lib/render-mode.cjs +54 -0
- package/dist-lib/scripts/dev-server-scanner.cjs +1232 -0
- package/dist-lib/sdk.cjs +2433 -0
- package/dist-lib/sdk.mjs +2336 -0
- package/dist-lib/skills.cjs +39132 -0
- package/dist-lib/swift-submit.cjs +92 -0
- package/dist-lib/swift-submit.mjs +65 -0
- package/dist-lib/swift.cjs +1646 -0
- package/dist-lib/vite.cjs +1591 -0
- package/package.json +290 -0
- package/scripts/dev-server-scanner.ts +1291 -0
- package/scripts/optional-demo-registry.ts +113 -0
- package/skills/contrast/SKILL.md +165 -0
- package/skills/rnx-debug/SKILL.md +433 -0
- package/skills/rnx-perf/SKILL.md +399 -0
- package/skills/rnx-setup/SKILL.md +172 -0
- package/skills/rnx-test/SKILL.md +321 -0
- package/skills/rnx-visual/SKILL.md +126 -0
- package/src/agent-daemon-client.ts +410 -0
- package/src/agent-events.ts +71 -0
- package/src/agent-identity.ts +23 -0
- package/src/agent-prompt.ts +71 -0
- package/src/agent-sessions.ts +580 -0
- package/src/app-url.ts +39 -0
- package/src/attached-projects.ts +539 -0
- package/src/auth/shared-session.ts +235 -0
- package/src/backend-origin.ts +70 -0
- package/src/beta.ts +19 -0
- package/src/bridge-constants.ts +175 -0
- package/src/bridge-contract-input.ts +502 -0
- package/src/bridge-contract.ts +558 -0
- package/src/browser-cache.ts +16 -0
- package/src/capture-contract.ts +587 -0
- package/src/cli-constants.ts +1 -0
- package/src/cli-version.ts +40 -0
- package/src/cloud-contract.ts +274 -0
- package/src/cloud.ts +762 -0
- package/src/codex-client.ts +234 -0
- package/src/config.ts +132 -0
- package/src/connect.ts +101 -0
- package/src/dev-bundle-resolution.ts +346 -0
- package/src/dev-server-open.ts +198 -0
- package/src/disk-cleanup.ts +540 -0
- package/src/flow-timeout-scale.ts +15 -0
- package/src/home-paths.ts +806 -0
- package/src/host/agent-host.ts +666 -0
- package/src/host/bridge-host.ts +2788 -0
- package/src/host/fetch-proxy-handler.ts +449 -0
- package/src/host/fetch-proxy-overrides.ts +51 -0
- package/src/host/open-url.ts +89 -0
- package/src/host/plane-host.ts +203 -0
- package/src/host/replacement-module-handler.ts +158 -0
- package/src/host/websocket-proxy.ts +315 -0
- package/src/index.ts +71 -0
- package/src/jump-to-source-babel.ts +135 -0
- package/src/jump-to-source-native.ts +102 -0
- package/src/menu.ts +777 -0
- package/src/metro-fingerprint-registry.ts +302 -0
- package/src/metro-plugin.ts +341 -0
- package/src/metro-production-bundle.ts +471 -0
- package/src/native-dev-bundle-url.ts +118 -0
- package/src/native-seam-manifest.ts +341 -0
- package/src/playwright-host-log.ts +16 -0
- package/src/plist.ts +27 -0
- package/src/poll-until.ts +123 -0
- package/src/profiles.ts +179 -0
- package/src/public-brand.ts +9 -0
- package/src/react-native-host-modules.ts +26 -0
- package/src/render-mode.ts +27 -0
- package/src/rolldown-plugin-swift.ts +240 -0
- package/src/runtime-assets.ts +233 -0
- package/src/runtime-delivery.ts +54 -0
- package/src/screenshots/compose.ts +422 -0
- package/src/screenshots/frame-compose.ts +514 -0
- package/src/screenshots/orchestrate.ts +256 -0
- package/src/screenshots/org-deck.ts +91 -0
- package/src/screenshots/registry.ts +58 -0
- package/src/screenshots/schema.ts +364 -0
- package/src/sdk.ts +15 -0
- package/src/settings-bundle.ts +418 -0
- package/src/shell-bridge-identity.ts +15 -0
- package/src/sim-client.ts +697 -0
- package/src/skills/builtin/a11y-review.ts +132 -0
- package/src/skills/builtin/compat-check.ts +71 -0
- package/src/skills/builtin/maestro-test.ts +121 -0
- package/src/skills/builtin/perf-profile.ts +90 -0
- package/src/skills/builtin/screenshot-all.ts +52 -0
- package/src/skills/builtin/visual-diff.ts +100 -0
- package/src/skills/registry.ts +107 -0
- package/src/skills/types.ts +41 -0
- package/src/swift-submit.ts +185 -0
- package/src/swift.ts +7 -0
- package/src/team-machine-prompt.ts +73 -0
- package/src/vite-plugin-one.ts +264 -0
- package/src/vite-plugin-swift.ts +620 -0
- package/src/vite-plugin.ts +1946 -0
- package/src/worklets-babel.ts +250 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
const RNXSIM_METRO_MODULE_PATHS_PREFIX = 'globalThis.__sootsimModulePaths='
|
|
2
|
+
export const RNX_METRO_MODULE_IDENTITY_VERSION = 2
|
|
3
|
+
|
|
4
|
+
export type RNXMetroModulePathMap = Record<string, string>
|
|
5
|
+
export type RNXMetroLogicalSpecifierMap = Record<string, string[]>
|
|
6
|
+
export type RNXMetroModuleIdentitySource =
|
|
7
|
+
| 'rnx-plugin'
|
|
8
|
+
| 'contrast-bundler'
|
|
9
|
+
| 'metro-source-map'
|
|
10
|
+
|
|
11
|
+
export interface RNXMetroModuleIdentityMetadata {
|
|
12
|
+
version: typeof RNX_METRO_MODULE_IDENTITY_VERSION
|
|
13
|
+
identitySource: RNXMetroModuleIdentitySource
|
|
14
|
+
modulePaths: RNXMetroModulePathMap
|
|
15
|
+
logicalSpecifiers: RNXMetroLogicalSpecifierMap
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface RNXMetroModuleIdentity extends RNXMetroModuleIdentityMetadata {
|
|
19
|
+
source: string
|
|
20
|
+
/**
|
|
21
|
+
* Metro's trailing `//# sourceMappingURL=` comment when the graph carried
|
|
22
|
+
* one, already split off `source`. A production graph has none.
|
|
23
|
+
*/
|
|
24
|
+
sourceMappingUrl: string | null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// the production bundle rnx loads is the app's ORDINARY production bundle.
|
|
28
|
+
// nothing about resolution changes, so these are plain Metro production
|
|
29
|
+
// options and the artifact is byte-identical to what the app ships apart
|
|
30
|
+
// from the identity footer the plugin's serializer appends.
|
|
31
|
+
export function toRNXProductionBundleUrl(bundleUrl: string): string {
|
|
32
|
+
const relative = bundleUrl.startsWith('/')
|
|
33
|
+
const url = new URL(bundleUrl, 'http://rnxsim.local')
|
|
34
|
+
url.searchParams.set('dev', 'false')
|
|
35
|
+
url.searchParams.set('hot', 'false')
|
|
36
|
+
url.searchParams.set('minify', 'true')
|
|
37
|
+
return relative ? `${url.pathname}${url.search}${url.hash}` : url.toString()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* the ordinary development counterpart. Metro keeps `__DEV__` true, verbose
|
|
42
|
+
* module names, and its source map. `hot` stays false because the artifact is
|
|
43
|
+
* immutable once produced.
|
|
44
|
+
*/
|
|
45
|
+
export function toRNXDevelopmentBundleUrl(bundleUrl: string): string {
|
|
46
|
+
const relative = bundleUrl.startsWith('/')
|
|
47
|
+
const url = new URL(bundleUrl, 'http://rnxsim.local')
|
|
48
|
+
url.searchParams.set('dev', 'true')
|
|
49
|
+
url.searchParams.set('hot', 'false')
|
|
50
|
+
url.searchParams.set('minify', 'false')
|
|
51
|
+
return relative ? `${url.pathname}${url.search}${url.hash}` : url.toString()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const RNXSIM_METRO_SOURCE_MAP_COMMENT = '//# sourceMappingURL='
|
|
55
|
+
|
|
56
|
+
export interface RNXMetroDetachedSourceMapUrl {
|
|
57
|
+
source: string
|
|
58
|
+
/** the comment's URL, which is a whole inline map when Metro inlined it. */
|
|
59
|
+
sourceMappingUrl: string | null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* splits Metro's trailing `//# sourceMappingURL=` comment off the bundle.
|
|
64
|
+
*
|
|
65
|
+
* a development bundle ends with that comment, and Metro inlines the entire
|
|
66
|
+
* map into it as a data URL whenever the caller did not ask for an external
|
|
67
|
+
* map file. leaving it attached makes the last `__r(` statement carry the map
|
|
68
|
+
* as source text: the layout reader would hand megabytes of base64 to the
|
|
69
|
+
* minifier, and the artifact the runner loads would keep the map resident in
|
|
70
|
+
* child memory for the life of the simulator.
|
|
71
|
+
*/
|
|
72
|
+
export function detachRNXMetroSourceMapUrl(
|
|
73
|
+
bundleText: string,
|
|
74
|
+
): RNXMetroDetachedSourceMapUrl {
|
|
75
|
+
const trimmedEnd = bundleText.replace(/\s+$/, '')
|
|
76
|
+
const commentStart = trimmedEnd.lastIndexOf(`\n${RNXSIM_METRO_SOURCE_MAP_COMMENT}`)
|
|
77
|
+
if (commentStart < 0) return { source: bundleText, sourceMappingUrl: null }
|
|
78
|
+
const url = trimmedEnd.slice(commentStart + 1 + RNXSIM_METRO_SOURCE_MAP_COMMENT.length)
|
|
79
|
+
if (url.includes('\n')) return { source: bundleText, sourceMappingUrl: null }
|
|
80
|
+
return { source: trimmedEnd.slice(0, commentStart), sourceMappingUrl: url }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function createRNXMetroModuleIdentityFooter(
|
|
84
|
+
identity: RNXMetroModuleIdentityMetadata,
|
|
85
|
+
): string {
|
|
86
|
+
const payload = JSON.stringify(identity)
|
|
87
|
+
.replace(/\u2028/g, '\\u2028')
|
|
88
|
+
.replace(/\u2029/g, '\\u2029')
|
|
89
|
+
.replace(/<\//g, '<\\/')
|
|
90
|
+
return `\n${RNXSIM_METRO_MODULE_PATHS_PREFIX}${payload};`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* writes the identity footer into a serialized bundle, keeping Metro's
|
|
95
|
+
* `//# sourceMappingURL=` comment on the last line when the graph has one. a
|
|
96
|
+
* production graph has no such comment and gets the footer appended, exactly
|
|
97
|
+
* as before.
|
|
98
|
+
*/
|
|
99
|
+
export function appendRNXMetroModuleIdentityFooter(
|
|
100
|
+
bundleText: string,
|
|
101
|
+
identity: RNXMetroModuleIdentityMetadata,
|
|
102
|
+
): string {
|
|
103
|
+
const footer = createRNXMetroModuleIdentityFooter(identity)
|
|
104
|
+
const detached = detachRNXMetroSourceMapUrl(bundleText)
|
|
105
|
+
return detached.sourceMappingUrl === null
|
|
106
|
+
? bundleText + footer
|
|
107
|
+
: `${detached.source}${footer}\n${RNXSIM_METRO_SOURCE_MAP_COMMENT}${detached.sourceMappingUrl}`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function readRNXMetroModuleIdentity(
|
|
111
|
+
bundleText: string,
|
|
112
|
+
): RNXMetroModuleIdentity | null {
|
|
113
|
+
const detached = detachRNXMetroSourceMapUrl(bundleText)
|
|
114
|
+
const withoutMap = detached.source
|
|
115
|
+
const footerPrefix = `\n${RNXSIM_METRO_MODULE_PATHS_PREFIX}`
|
|
116
|
+
const footerStart = withoutMap.lastIndexOf(footerPrefix)
|
|
117
|
+
if (footerStart < 0) return null
|
|
118
|
+
if (!withoutMap.endsWith(';')) {
|
|
119
|
+
throw new Error('Metro module identity footer is truncated')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let parsed: unknown
|
|
123
|
+
try {
|
|
124
|
+
parsed = JSON.parse(withoutMap.slice(footerStart + footerPrefix.length, -1))
|
|
125
|
+
} catch {
|
|
126
|
+
throw new Error('Metro module identity footer is not valid JSON')
|
|
127
|
+
}
|
|
128
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
129
|
+
throw new Error('Metro module identity footer is malformed')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const version = Reflect.get(parsed, 'version')
|
|
133
|
+
// stored artifacts from before the versioned footer carry a bare
|
|
134
|
+
// `{"<moduleId>":"<path>", ...}` map with no wrapper — the exact shape
|
|
135
|
+
// appendRNXModulePaths wrote before the footer was versioned, and the only
|
|
136
|
+
// writer of that shape used identitySource 'rnx-plugin'. normalize it into
|
|
137
|
+
// the current in-memory identity so callers get the same exact modulePaths
|
|
138
|
+
// map instead of falling back to name-scanning or fingerprint inference.
|
|
139
|
+
// validateRNXMetroModuleIdentity still detects a missing, extra, duplicate,
|
|
140
|
+
// or truncated module ID against the bundle's own __d() defines, so those
|
|
141
|
+
// legacy-map corruptions are caught there, not trusted here — it does not
|
|
142
|
+
// corroborate that a path or factory is itself correct.
|
|
143
|
+
if (version === undefined) {
|
|
144
|
+
const modulePaths: RNXMetroModulePathMap = Object.fromEntries(
|
|
145
|
+
Object.entries(parsed).map(([moduleId, modulePath]) => {
|
|
146
|
+
if (typeof modulePath !== 'string') {
|
|
147
|
+
throw new Error(`Metro module identity has an invalid path for ${moduleId}`)
|
|
148
|
+
}
|
|
149
|
+
return [moduleId, modulePath]
|
|
150
|
+
}),
|
|
151
|
+
)
|
|
152
|
+
return {
|
|
153
|
+
source: withoutMap.slice(0, footerStart),
|
|
154
|
+
version: RNX_METRO_MODULE_IDENTITY_VERSION,
|
|
155
|
+
identitySource: 'rnx-plugin',
|
|
156
|
+
modulePaths,
|
|
157
|
+
logicalSpecifiers: {},
|
|
158
|
+
sourceMappingUrl: detached.sourceMappingUrl,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const identitySource = Reflect.get(parsed, 'identitySource')
|
|
162
|
+
const parsedModulePaths = Reflect.get(parsed, 'modulePaths')
|
|
163
|
+
const parsedLogicalSpecifiers = Reflect.get(parsed, 'logicalSpecifiers')
|
|
164
|
+
if (
|
|
165
|
+
version !== RNX_METRO_MODULE_IDENTITY_VERSION ||
|
|
166
|
+
(identitySource !== 'rnx-plugin' &&
|
|
167
|
+
identitySource !== 'contrast-bundler' &&
|
|
168
|
+
identitySource !== 'metro-source-map') ||
|
|
169
|
+
typeof parsedModulePaths !== 'object' ||
|
|
170
|
+
parsedModulePaths === null ||
|
|
171
|
+
Array.isArray(parsedModulePaths) ||
|
|
172
|
+
typeof parsedLogicalSpecifiers !== 'object' ||
|
|
173
|
+
parsedLogicalSpecifiers === null ||
|
|
174
|
+
Array.isArray(parsedLogicalSpecifiers)
|
|
175
|
+
) {
|
|
176
|
+
throw new Error('Metro module identity footer is malformed')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const modulePaths: RNXMetroModulePathMap = Object.fromEntries(
|
|
180
|
+
Object.entries(parsedModulePaths).map(([moduleId, modulePath]) => {
|
|
181
|
+
if (typeof modulePath !== 'string') {
|
|
182
|
+
throw new Error(`Metro module identity has an invalid path for ${moduleId}`)
|
|
183
|
+
}
|
|
184
|
+
return [moduleId, modulePath]
|
|
185
|
+
}),
|
|
186
|
+
)
|
|
187
|
+
const logicalSpecifiers: RNXMetroLogicalSpecifierMap = Object.fromEntries(
|
|
188
|
+
Object.entries(parsedLogicalSpecifiers).map(([moduleId, specifiers]) => {
|
|
189
|
+
if (
|
|
190
|
+
!Array.isArray(specifiers) ||
|
|
191
|
+
specifiers.length === 0 ||
|
|
192
|
+
!specifiers.every(
|
|
193
|
+
(specifier) => typeof specifier === 'string' && specifier.length > 0,
|
|
194
|
+
)
|
|
195
|
+
) {
|
|
196
|
+
throw new Error(`Metro module identity has invalid specifiers for ${moduleId}`)
|
|
197
|
+
}
|
|
198
|
+
const unique = [...new Set(specifiers)].sort((left, right) =>
|
|
199
|
+
left.localeCompare(right),
|
|
200
|
+
)
|
|
201
|
+
if (unique.length !== specifiers.length) {
|
|
202
|
+
throw new Error(`Metro module identity repeats a specifier for ${moduleId}`)
|
|
203
|
+
}
|
|
204
|
+
return [moduleId, unique]
|
|
205
|
+
}),
|
|
206
|
+
)
|
|
207
|
+
return {
|
|
208
|
+
source: withoutMap.slice(0, footerStart),
|
|
209
|
+
version,
|
|
210
|
+
identitySource,
|
|
211
|
+
modulePaths,
|
|
212
|
+
logicalSpecifiers,
|
|
213
|
+
sourceMappingUrl: detached.sourceMappingUrl,
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// the exact layout Metro's baseJSBundle emits and the rnx runtime contract
|
|
218
|
+
// depends on: a prelude, then one `__d(` or `__r(` statement per line, each
|
|
219
|
+
// running until the next statement starts its own line. one reader owns this
|
|
220
|
+
// recognition so the loader's identity validation, the source-map attribution
|
|
221
|
+
// pass, and the cloud artifact producer cannot drift apart.
|
|
222
|
+
export interface RNXMetroDefine {
|
|
223
|
+
kind: 'define'
|
|
224
|
+
moduleId: number
|
|
225
|
+
start: number
|
|
226
|
+
end: number
|
|
227
|
+
lineIndex: number
|
|
228
|
+
endLineIndex: number
|
|
229
|
+
/** the factory expression, between `__d(` and the module ID. */
|
|
230
|
+
factory: { start: number; end: number }
|
|
231
|
+
/** the dependency array, brackets included. */
|
|
232
|
+
dependencies: { start: number; end: number }
|
|
233
|
+
/**
|
|
234
|
+
* the module's verbose name, quotes included, when the graph carries one.
|
|
235
|
+
* Metro emits it as a fourth `__d` argument on a development graph and omits
|
|
236
|
+
* it entirely on a production one.
|
|
237
|
+
*/
|
|
238
|
+
verboseName?: { start: number; end: number }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface RNXMetroRun {
|
|
242
|
+
kind: 'run'
|
|
243
|
+
start: number
|
|
244
|
+
end: number
|
|
245
|
+
lineIndex: number
|
|
246
|
+
endLineIndex: number
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export type RNXMetroStatement = RNXMetroDefine | RNXMetroRun
|
|
250
|
+
|
|
251
|
+
export interface RNXMetroBundleLayout {
|
|
252
|
+
/** everything before the first `__d(`/`__r(` line: prelude vars and polyfills. */
|
|
253
|
+
prelude: string
|
|
254
|
+
statements: RNXMetroStatement[]
|
|
255
|
+
/** `__d(` statements whose module ID could not be read. */
|
|
256
|
+
moduleIdFailures: number
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
interface DefineTail {
|
|
260
|
+
moduleId: number
|
|
261
|
+
factoryEnd: number
|
|
262
|
+
dependenciesStart: number
|
|
263
|
+
dependenciesEnd: number
|
|
264
|
+
verboseNameStart?: number
|
|
265
|
+
verboseNameEnd?: number
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// walks back over a single-quoted or double-quoted string literal ending at
|
|
269
|
+
// `end`, returning the index of its opening quote. an odd run of backslashes
|
|
270
|
+
// before a quote escapes it, so count them before accepting a candidate.
|
|
271
|
+
function readStringLiteralStart(source: string, start: number, end: number): number {
|
|
272
|
+
const quote = source[end]
|
|
273
|
+
if (quote !== '"' && quote !== "'") return -1
|
|
274
|
+
for (let index = end - 1; index > start; index--) {
|
|
275
|
+
if (source[index] !== quote) continue
|
|
276
|
+
let backslashes = 0
|
|
277
|
+
while (index - 1 - backslashes > start && source[index - 1 - backslashes] === '\\') {
|
|
278
|
+
backslashes++
|
|
279
|
+
}
|
|
280
|
+
if (backslashes % 2 === 0) return index
|
|
281
|
+
}
|
|
282
|
+
return -1
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// each `__d(...)` ends with `),<id>,[dep,dep,...]);?`, and a development graph
|
|
286
|
+
// adds a fourth verbose-name string after the dependency array. the dependency
|
|
287
|
+
// array can be hundreds of entries long, so walk back from `end` instead of
|
|
288
|
+
// searching a fixed tail window: skip trailing `;` and whitespace, consume the
|
|
289
|
+
// closing `)`, take the verbose name when one is there, balance `[...]` to find
|
|
290
|
+
// its opening bracket, then read the integer before it.
|
|
291
|
+
function readDefineTail(source: string, start: number, end: number): DefineTail | null {
|
|
292
|
+
let cursor = end
|
|
293
|
+
while (
|
|
294
|
+
cursor > start &&
|
|
295
|
+
(source[cursor] === ' ' ||
|
|
296
|
+
source[cursor] === '\n' ||
|
|
297
|
+
source[cursor] === '\t' ||
|
|
298
|
+
source[cursor] === ';')
|
|
299
|
+
) {
|
|
300
|
+
cursor--
|
|
301
|
+
}
|
|
302
|
+
if (source[cursor] !== ')') return null
|
|
303
|
+
cursor--
|
|
304
|
+
while (cursor > start && /\s/.test(source[cursor])) cursor--
|
|
305
|
+
let verboseNameStart: number | undefined
|
|
306
|
+
let verboseNameEnd: number | undefined
|
|
307
|
+
if (source[cursor] === '"' || source[cursor] === "'") {
|
|
308
|
+
const literalStart = readStringLiteralStart(source, start, cursor)
|
|
309
|
+
if (literalStart < 0) return null
|
|
310
|
+
verboseNameStart = literalStart
|
|
311
|
+
verboseNameEnd = cursor + 1
|
|
312
|
+
cursor = literalStart - 1
|
|
313
|
+
while (cursor > start && /\s/.test(source[cursor])) cursor--
|
|
314
|
+
if (source[cursor] !== ',') return null
|
|
315
|
+
cursor--
|
|
316
|
+
while (cursor > start && /\s/.test(source[cursor])) cursor--
|
|
317
|
+
}
|
|
318
|
+
if (source[cursor] !== ']') return null
|
|
319
|
+
const dependenciesEnd = cursor + 1
|
|
320
|
+
let depth = 1
|
|
321
|
+
cursor--
|
|
322
|
+
while (cursor > start && depth > 0) {
|
|
323
|
+
const character = source[cursor]
|
|
324
|
+
if (character === ']') depth++
|
|
325
|
+
else if (character === '[') depth--
|
|
326
|
+
if (depth === 0) break
|
|
327
|
+
cursor--
|
|
328
|
+
}
|
|
329
|
+
if (depth !== 0) return null
|
|
330
|
+
const dependenciesStart = cursor
|
|
331
|
+
let idEnd = cursor - 1
|
|
332
|
+
while (idEnd > start && /\s/.test(source[idEnd])) idEnd--
|
|
333
|
+
if (source[idEnd] !== ',') return null
|
|
334
|
+
// a minifier is free to write the module ID as `1e3` or `0x3e8`, so read the
|
|
335
|
+
// whole numeric literal rather than a run of decimal digits. the factory
|
|
336
|
+
// before it always ends in `}` or `)`, which stops this walk immediately.
|
|
337
|
+
let idStart = idEnd
|
|
338
|
+
while (idStart > start && /[0-9a-fA-FxX.+-]/.test(source[idStart - 1])) idStart--
|
|
339
|
+
const literal = source.slice(idStart, idEnd)
|
|
340
|
+
if (!/^-?(?:0[xXbBoO][0-9a-fA-F]+|\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)$/.test(literal)) {
|
|
341
|
+
return null
|
|
342
|
+
}
|
|
343
|
+
const moduleId = Number(literal)
|
|
344
|
+
if (!Number.isSafeInteger(moduleId)) return null
|
|
345
|
+
let factoryEnd = idStart - 1
|
|
346
|
+
while (factoryEnd > start && /\s/.test(source[factoryEnd])) factoryEnd--
|
|
347
|
+
if (source[factoryEnd] !== ',') return null
|
|
348
|
+
return {
|
|
349
|
+
moduleId,
|
|
350
|
+
factoryEnd,
|
|
351
|
+
dependenciesStart,
|
|
352
|
+
dependenciesEnd,
|
|
353
|
+
verboseNameStart,
|
|
354
|
+
verboseNameEnd,
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** reads the canonical Metro production layout from footer-stripped source. */
|
|
359
|
+
export function readRNXMetroBundleLayout(source: string): RNXMetroBundleLayout {
|
|
360
|
+
const lineOffsets = [0]
|
|
361
|
+
for (let index = 0; index < source.length; index++) {
|
|
362
|
+
if (source.charCodeAt(index) === 10) lineOffsets.push(index + 1)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const boundaries: number[] = []
|
|
366
|
+
for (let lineIndex = 0; lineIndex < lineOffsets.length; lineIndex++) {
|
|
367
|
+
const start = lineOffsets[lineIndex]
|
|
368
|
+
if (source.startsWith('__d(', start) || source.startsWith('__r(', start)) {
|
|
369
|
+
boundaries.push(lineIndex)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const statements: RNXMetroStatement[] = []
|
|
374
|
+
let moduleIdFailures = 0
|
|
375
|
+
for (let index = 0; index < boundaries.length; index++) {
|
|
376
|
+
const lineIndex = boundaries[index]
|
|
377
|
+
const start = lineOffsets[lineIndex]
|
|
378
|
+
const nextLineIndex = boundaries[index + 1]
|
|
379
|
+
const endLineIndex =
|
|
380
|
+
nextLineIndex !== undefined ? nextLineIndex - 1 : lineOffsets.length - 1
|
|
381
|
+
const end = nextLineIndex !== undefined ? lineOffsets[nextLineIndex] : source.length
|
|
382
|
+
if (!source.startsWith('__d(', start)) {
|
|
383
|
+
statements.push({ kind: 'run', start, end, lineIndex, endLineIndex })
|
|
384
|
+
continue
|
|
385
|
+
}
|
|
386
|
+
const tail = readDefineTail(source, start, end - 1)
|
|
387
|
+
if (!tail) {
|
|
388
|
+
moduleIdFailures++
|
|
389
|
+
continue
|
|
390
|
+
}
|
|
391
|
+
statements.push({
|
|
392
|
+
kind: 'define',
|
|
393
|
+
moduleId: tail.moduleId,
|
|
394
|
+
start,
|
|
395
|
+
end,
|
|
396
|
+
lineIndex,
|
|
397
|
+
endLineIndex,
|
|
398
|
+
factory: { start: start + '__d('.length, end: tail.factoryEnd },
|
|
399
|
+
dependencies: { start: tail.dependenciesStart, end: tail.dependenciesEnd },
|
|
400
|
+
...(tail.verboseNameStart !== undefined && tail.verboseNameEnd !== undefined
|
|
401
|
+
? { verboseName: { start: tail.verboseNameStart, end: tail.verboseNameEnd } }
|
|
402
|
+
: {}),
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
prelude: source.slice(
|
|
408
|
+
0,
|
|
409
|
+
boundaries.length > 0 ? lineOffsets[boundaries[0]] : source.length,
|
|
410
|
+
),
|
|
411
|
+
statements,
|
|
412
|
+
moduleIdFailures,
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* validates the exact identity contract before a loader installs a Metro
|
|
418
|
+
* factory trap or a producer ships an artifact: every define carries a unique,
|
|
419
|
+
* readable module ID, and the identity map names those IDs and no others.
|
|
420
|
+
* serializers that emit another layout need an ingestion adapter that
|
|
421
|
+
* normalizes them to this format.
|
|
422
|
+
*/
|
|
423
|
+
export function validateRNXMetroModulePaths(
|
|
424
|
+
source: string,
|
|
425
|
+
modulePaths: RNXMetroModulePathMap,
|
|
426
|
+
): void {
|
|
427
|
+
const layout = readRNXMetroBundleLayout(source)
|
|
428
|
+
if (layout.moduleIdFailures > 0) {
|
|
429
|
+
throw new Error(
|
|
430
|
+
`Metro module identity could not read ${layout.moduleIdFailures} module IDs`,
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
const definedIds = new Set<number>()
|
|
434
|
+
for (const statement of layout.statements) {
|
|
435
|
+
if (statement.kind !== 'define') continue
|
|
436
|
+
if (definedIds.has(statement.moduleId)) {
|
|
437
|
+
throw new Error(`Metro module identity has duplicate module ${statement.moduleId}`)
|
|
438
|
+
}
|
|
439
|
+
definedIds.add(statement.moduleId)
|
|
440
|
+
if (!Object.prototype.hasOwnProperty.call(modulePaths, String(statement.moduleId))) {
|
|
441
|
+
throw new Error(`Metro module identity is missing ${statement.moduleId}`)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (definedIds.size === 0) {
|
|
445
|
+
throw new Error('Metro module identity found no canonical JavaScript modules')
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
for (const [key, modulePath] of Object.entries(modulePaths)) {
|
|
449
|
+
const moduleId = Number(key)
|
|
450
|
+
if (!Number.isSafeInteger(moduleId) || String(moduleId) !== key) {
|
|
451
|
+
throw new Error(`Metro module identity has invalid module ID ${key}`)
|
|
452
|
+
}
|
|
453
|
+
if (typeof modulePath !== 'string' || modulePath.length === 0) {
|
|
454
|
+
throw new Error(`Metro module identity has no path for ${key}`)
|
|
455
|
+
}
|
|
456
|
+
if (!definedIds.has(moduleId)) {
|
|
457
|
+
throw new Error(`Metro module identity has no module ${key}`)
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function validateRNXMetroModuleIdentity(identity: RNXMetroModuleIdentity): void {
|
|
463
|
+
validateRNXMetroModulePaths(identity.source, identity.modulePaths)
|
|
464
|
+
for (const moduleId of Object.keys(identity.logicalSpecifiers)) {
|
|
465
|
+
if (!Object.prototype.hasOwnProperty.call(identity.modulePaths, moduleId)) {
|
|
466
|
+
throw new Error(
|
|
467
|
+
`Metro module identity has logical specifiers for unknown module ${moduleId}`,
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { isLoopbackHost } from './backend-origin.ts'
|
|
2
|
+
|
|
3
|
+
// canonical entry path for a plain Metro packager that serves no Expo
|
|
4
|
+
// manifest. only used when `/status` confirms `packager-status:running` and
|
|
5
|
+
// the server reports nothing else — never as a guess for a manifest-capable
|
|
6
|
+
// dev server, whose `launchAsset.url` is always the source of truth.
|
|
7
|
+
export const METRO_BUNDLE_PATH =
|
|
8
|
+
'/index.bundle?platform=ios&dev=true&hot=true&minify=false'
|
|
9
|
+
|
|
10
|
+
function isAbsoluteHttpUrl(url: string): boolean {
|
|
11
|
+
return /^https?:\/\//i.test(url)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isNativeDevBundlePath(pathname: string): boolean {
|
|
15
|
+
return pathname.endsWith('.bundle')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// expo can advertise an IPv4 loopback launch URL even when its --localhost
|
|
19
|
+
// listener resolved to IPv6 only. keep the manifest's path and query, but bind
|
|
20
|
+
// its loopback host to the base URL that answered the discovery request.
|
|
21
|
+
export function alignLoopbackBundleUrlWithProbe(
|
|
22
|
+
bundleUrl: string,
|
|
23
|
+
probeBaseUrl: string,
|
|
24
|
+
): string {
|
|
25
|
+
try {
|
|
26
|
+
const bundle = new URL(bundleUrl)
|
|
27
|
+
const probe = new URL(probeBaseUrl)
|
|
28
|
+
if (!isLoopbackHost(bundle.hostname) || !isLoopbackHost(probe.hostname)) {
|
|
29
|
+
return bundleUrl
|
|
30
|
+
}
|
|
31
|
+
bundle.hostname = probe.hostname
|
|
32
|
+
return bundle.toString()
|
|
33
|
+
} catch {
|
|
34
|
+
return bundleUrl
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// opt-in (contrastbot preview-recording context only): serve a production
|
|
39
|
+
// minified bundle instead of the default dev=true unminified one. a
|
|
40
|
+
// preview run just records the app — it never needs fast-refresh or dev
|
|
41
|
+
// warnings — and the ~28MB unminified dev bundle cold-(re)boots
|
|
42
|
+
// unreliably on small CI runners (tens of seconds when metro is warm,
|
|
43
|
+
// minutes cold, sometimes past any wait-ready budget). a minified bundle
|
|
44
|
+
// parses/executes far faster and reboots reliably. gated on
|
|
45
|
+
// SOOTSIM_PREVIEW_PROD_BUNDLE so normal interactive `rnx open` keeps
|
|
46
|
+
// dev=true. app-agnostic: rewrites whatever metro/launchAsset advertised.
|
|
47
|
+
function applyPreviewProdBundle(parsed: URL): void {
|
|
48
|
+
if (!process.env.SOOTSIM_PREVIEW_PROD_BUNDLE) return
|
|
49
|
+
parsed.searchParams.set('dev', 'false')
|
|
50
|
+
parsed.searchParams.set('minify', 'true')
|
|
51
|
+
// prod bundles have no HMR; keep metro's cache key consistent.
|
|
52
|
+
if (parsed.searchParams.has('hot')) parsed.searchParams.set('hot', 'false')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function normalizeNativeDevBundleUrl(bundleUrl: string): string {
|
|
56
|
+
try {
|
|
57
|
+
const isAbsolute = isAbsoluteHttpUrl(bundleUrl)
|
|
58
|
+
const parsed = new URL(bundleUrl, 'http://sootsim.local')
|
|
59
|
+
parsed.pathname = parsed.pathname.replace(/\.\.bundle$/, '.bundle')
|
|
60
|
+
if (!isNativeDevBundlePath(parsed.pathname)) return bundleUrl
|
|
61
|
+
// do NOT force dev=true or minify=false — metro's dev server already
|
|
62
|
+
// defaults to dev mode + unminified, and iOS sim's launchAsset URL
|
|
63
|
+
// omits both params. forcing them gives metro a different cache key
|
|
64
|
+
// for the same bundle. only the prod-bundle override path
|
|
65
|
+
// (demo-upload-specs) sets dev=false&minify=true on purpose.
|
|
66
|
+
// do NOT touch `hot` either. real RN iOS clients load launchAsset.url
|
|
67
|
+
// verbatim — expo's createBundleUrlSearchParams hardcodes hot=false
|
|
68
|
+
// (with a `// TODO: Is this still needed?` comment) for every manifest
|
|
69
|
+
// it emits, and HMR still works on iOS because RN's HMRClient.js
|
|
70
|
+
// polyfill connects to /hot independently. sootsim's HmrClient is the
|
|
71
|
+
// same — connection happens via /hot regardless of this query param —
|
|
72
|
+
// so matching the iOS sim's URL exactly lets metro share its bundle
|
|
73
|
+
// cache between the two clients (otherwise the 28MB+ bundle rebuilds
|
|
74
|
+
// twice on first cold metro request).
|
|
75
|
+
// strip transform.bytecode — metro would return hermes bytecode and our
|
|
76
|
+
// bundle-loader can only execute plain JS.
|
|
77
|
+
parsed.searchParams.delete('transform.bytecode')
|
|
78
|
+
// force lazy=false. sootsim loads ONE self-contained bundle into the tenant
|
|
79
|
+
// worker. metro's lazy=true defers code-split modules to async chunks; vxrn/
|
|
80
|
+
// one's emitted entry can synchronously `require` a deferred module before
|
|
81
|
+
// its chunk has loaded, throwing "Requiring unknown module <id>" and crashing
|
|
82
|
+
// the route (observed with one's React.lazy GameScene3D split). lazy=false
|
|
83
|
+
// ships every module in the served bundle — import()/React.lazy RUNTIME
|
|
84
|
+
// semantics are unchanged (the chunk is just inlined), so this is faithful,
|
|
85
|
+
// not a behavior change. the engine's __loadBundleAsync still handles any
|
|
86
|
+
// genuinely-async chunks an app fetches at runtime.
|
|
87
|
+
if (parsed.searchParams.get('lazy') === 'true') {
|
|
88
|
+
parsed.searchParams.set('lazy', 'false')
|
|
89
|
+
}
|
|
90
|
+
applyPreviewProdBundle(parsed)
|
|
91
|
+
if (isAbsolute) return parsed.toString()
|
|
92
|
+
return `${parsed.pathname}${parsed.search}${parsed.hash}`
|
|
93
|
+
} catch {
|
|
94
|
+
return bundleUrl
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// explicit override used by `rnx open` to flip metro's `hot` query when
|
|
99
|
+
// the caller knows their context: interactive opens want `hot=true` so metro
|
|
100
|
+
// includes its HMR/react-refresh runtime in the bundle prelude and pushes
|
|
101
|
+
// edits as `update` events; `--driver playwright` / agent-test runs want
|
|
102
|
+
// `hot=false` for deterministic e2e (no in-bundle refresh wiring, no
|
|
103
|
+
// background ws). normalizeNativeDevBundleUrl deliberately preserves
|
|
104
|
+
// whatever value the launchAsset URL carries so metro's bundle cache is
|
|
105
|
+
// shared with real iOS sims; this helper is the one place that breaks that
|
|
106
|
+
// rule on purpose. non-`.bundle` paths pass through unchanged.
|
|
107
|
+
export function applyHotMode(bundleUrl: string, hot: boolean): string {
|
|
108
|
+
try {
|
|
109
|
+
const isAbsolute = isAbsoluteHttpUrl(bundleUrl)
|
|
110
|
+
const parsed = new URL(bundleUrl, 'http://sootsim.local')
|
|
111
|
+
if (!isNativeDevBundlePath(parsed.pathname)) return bundleUrl
|
|
112
|
+
parsed.searchParams.set('hot', hot ? 'true' : 'false')
|
|
113
|
+
if (isAbsolute) return parsed.toString()
|
|
114
|
+
return `${parsed.pathname}${parsed.search}${parsed.hash}`
|
|
115
|
+
} catch {
|
|
116
|
+
return bundleUrl
|
|
117
|
+
}
|
|
118
|
+
}
|