peachsim 0.1.939 → 0.1.1399
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/README.md +28 -22
- package/cli/app-catalog.ts +29 -14
- package/cli/app-fonts.ts +5 -3
- package/cli/bridge-flow-runner.ts +40 -29
- package/cli/cloud-client.ts +57 -21
- package/cli/cloud-test-tunnel.ts +12 -18
- package/cli/commands/agent.ts +1 -1
- package/cli/commands/app-fonts.ts +3 -3
- package/cli/commands/assert.ts +8 -8
- package/cli/commands/auth.ts +8 -3
- package/cli/commands/box/bundle-plane.ts +13 -1
- package/cli/commands/box.ts +16 -5
- package/cli/commands/compat.ts +1 -1
- package/cli/commands/config.ts +2 -2
- package/cli/commands/control.ts +125 -71
- package/cli/commands/cpu-profile.ts +45 -1
- package/cli/commands/daemon.ts +131 -33
- package/cli/commands/debug.ts +24 -17
- package/cli/commands/desktop.ts +7 -1
- package/cli/commands/detect.ts +101 -0
- package/cli/commands/detox.ts +6 -6
- package/cli/commands/device.ts +12 -12
- package/cli/commands/electron.ts +32 -14
- package/cli/commands/flow.ts +49 -41
- package/cli/commands/guest-host.ts +39 -1
- package/cli/commands/inspect/core.ts +14 -8
- package/cli/commands/inspect/list.ts +1 -1
- package/cli/commands/inspect/settle.ts +5 -3
- package/cli/commands/inspect/settling.ts +229 -44
- package/cli/commands/inspect/wait-idle.ts +3 -2
- package/cli/commands/inspect.ts +105 -93
- package/cli/commands/install-cli.ts +36 -15
- package/cli/commands/install-desktop.ts +68 -195
- package/cli/commands/kotlin.ts +6 -5
- package/cli/commands/login.ts +5 -5
- package/cli/commands/maestro-compare.ts +554 -0
- package/cli/commands/maestro-generate.ts +6 -3
- package/cli/commands/maestro.ts +22 -0
- package/cli/commands/mode.ts +2 -2
- package/cli/commands/no-bridge-hint.ts +6 -2
- package/cli/commands/platform.ts +35 -6
- package/cli/commands/preview.ts +6 -0
- package/cli/commands/project-report.ts +10 -12
- package/cli/commands/record.ts +37 -37
- package/cli/commands/runtime.ts +9 -9
- package/cli/commands/screenshot-layers.ts +4 -4
- package/cli/commands/screenshot-mode.ts +2 -2
- package/cli/commands/screenshot.ts +8 -8
- package/cli/commands/screenshots-capture.ts +2 -2
- package/cli/commands/serve.ts +9 -12
- package/cli/commands/setup.ts +29 -14
- package/cli/commands/shell-boolean-mode.ts +2 -2
- package/cli/commands/show-touches-mode.ts +2 -2
- package/cli/commands/skills.ts +11 -11
- package/cli/commands/swift.ts +29 -11
- package/cli/commands/telemetry.ts +2 -2
- package/cli/commands/test.ts +5 -0
- package/cli/commands/three-mode.ts +3 -3
- package/cli/commands/timeline.ts +4 -4
- package/cli/commands/upgrade.ts +2 -2
- package/cli/commands/upload.ts +15 -15
- package/cli/commands/what-happened.ts +2 -2
- package/cli/current-sim.ts +6 -3
- package/cli/desktop-companion.ts +55 -149
- package/cli/dev-build-freshness.ts +2 -2
- package/cli/drivers/desktop.ts +17 -9
- package/cli/drivers/electron.ts +1 -2
- package/cli/drivers/playwright-sim-host.ts +11 -2
- package/cli/drivers/playwright.ts +35 -2
- package/cli/drivers/types.ts +8 -0
- package/cli/maestro-compare.ts +670 -0
- package/cli/outbound-endpoints.ts +46 -8
- package/cli/recording-access.ts +6 -6
- package/cli/run-registry.ts +1 -1
- package/cli/runtime-compat.ts +5 -1
- package/cli/send-to-box.ts +3 -3
- package/cli/shell-init.ts +2 -1
- package/cli/ws-bridge.ts +9 -5
- package/detox/element-types.ts +1 -0
- package/detox/gestures.ts +3 -3
- package/detox/index.ts +67 -38
- package/detox/navigation-lifecycle.ts +9 -6
- package/detox/proof-frame.cjs +6 -4
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.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-input.cjs +42 -17
- package/dist-lib/bridge-contract-input.mjs +41 -16
- package/dist-lib/bridge-contract.cjs +1 -4
- package/dist-lib/bridge-contract.mjs +1 -3
- package/dist-lib/bundle-channel.cjs +2918 -550
- package/dist-lib/capture-contract.cjs +65 -24
- package/dist-lib/capture-contract.mjs +63 -24
- package/dist-lib/cloud-contract.cjs +2 -2
- package/dist-lib/cloud-contract.mjs +2 -2
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +3 -3
- package/dist-lib/cloud.mjs +3 -3
- package/dist-lib/config.cjs +15 -5
- package/dist-lib/config.mjs +154 -0
- package/dist-lib/detox/index.cjs +495 -139
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/expo-swift-bundle.cjs +1168 -238
- package/dist-lib/expo-swift-metadata.cjs +34 -23
- package/dist-lib/expo-swift-metadata.mjs +32 -23
- package/dist-lib/home-paths.cjs +2 -2
- package/dist-lib/host/bridge-host.cjs +2938 -654
- package/dist-lib/host/fetch-proxy-handler.cjs +14 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +7 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +5 -1
- package/dist-lib/host/replacement-module-handler.cjs +2 -2
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +999 -353
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/kotlin-host.cjs +332 -19
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +7 -7
- package/dist-lib/metro-production-bundle.mjs +5 -5
- package/dist-lib/metro.cjs +688 -250
- package/dist-lib/native-app-identity-project.cjs +2647 -0
- package/dist-lib/native-app-identity-project.mjs +1257 -0
- package/dist-lib/native-app-identity.cjs +325 -25
- package/dist-lib/native-app-identity.mjs +323 -25
- package/dist-lib/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.mjs +1 -1
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +5 -1
- package/dist-lib/react-native-host-modules.mjs +5 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +57 -32
- package/dist-lib/sdk.cjs +231 -51
- package/dist-lib/sdk.mjs +231 -51
- package/dist-lib/skills.cjs +4107 -1374
- package/dist-lib/swift-compile.cjs +290 -30
- package/dist-lib/swift-compile.mjs +290 -30
- package/dist-lib/swift-package.cjs +143 -12
- package/dist-lib/swift-package.mjs +137 -11
- package/dist-lib/swift-submit.cjs +47 -5
- package/dist-lib/swift-submit.mjs +42 -5
- package/dist-lib/swift-toolchain.cjs +7 -2
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +1392 -458
- package/dist-lib/vite.cjs +13 -17
- package/dist-lib/worklet-executable.cjs +248 -33
- package/dist-lib/worklet-executable.mjs +248 -33
- package/install.sh +10 -1
- package/kotlin/{AppSources → AndroidViewSources}/android/content/res/Resources.kt +9 -9
- package/kotlin/AndroidViewSources/android/graphics/Canvas.kt +20 -0
- package/kotlin/AndroidViewSources/android/view/View.kt +211 -0
- package/kotlin/AndroidViewSources/android/widget/FrameLayout.kt +61 -0
- package/kotlin/AndroidViewSources/android/widget/LinearLayout.kt +49 -0
- package/kotlin/AppSources/android/view/Window.kt +36 -0
- package/kotlin/AppSources/android/view/WindowInsets.kt +9 -0
- package/kotlin/AppSources/android/view/WindowInsetsController.kt +5 -0
- package/kotlin/AppSources/android/window/OnBackInvokedDispatcher.kt +48 -0
- package/kotlin/AppSources/androidx/activity/OnBackPressedDispatcher.kt +20 -5
- package/kotlin/AppSources/androidx/compose/foundation/layout/WindowInsets.kt +15 -0
- package/kotlin/AppSources/androidx/compose/ui/platform/ComposeView.kt +2 -1
- package/kotlin/AppSources/dev/peach/app/PeachApp.kt +42 -50
- package/kotlin/ComposeSources/PeachComposeHost.kt +200 -84
- package/kotlin/ExpoComposeSources/PeachAndroidView.kt +87 -0
- package/kotlin/ExpoComposeSources/PeachExpoViewMount.kt +24 -0
- package/kotlin/ExpoComposeSources/dev/peach/expo/PeachExpoCompose.kt +28 -0
- package/kotlin/ExpoComposeSources/expo/modules/kotlin/views/ExpoComposeView.kt +70 -0
- package/kotlin/ExpoSources/dev/peach/expo/PeachExpoRuntime.kt +513 -0
- package/kotlin/ExpoSources/dev/peach/expo/PeachExpoTypes.kt +173 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/AppContext.kt +20 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/Promise.kt +19 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/classcomponent/ClassComponentBuilder.kt +198 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/exception/CodedException.kt +126 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/functions/Functions.kt +132 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/jni/JavaScriptTypedArray.kt +81 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/modules/Module.kt +98 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/objects/ObjectDefinitionBuilder.kt +236 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/records/Record.kt +12 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/runtime/Runtime.kt +9 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedObject.kt +50 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedObjectRegistry.kt +57 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedRef.kt +19 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/ConcreteTypedArrays.kt +158 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/GenericTypedArray.kt +8 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/TypedArray.kt +29 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/TypedArrayIterator.kt +9 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/types/AnyType.kt +155 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/types/Either.kt +91 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/types/Enumerable.kt +6 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/viewevent/ViewEvent.kt +54 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/views/ExpoView.kt +23 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewDefinitionBuilder.kt +71 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewProps.kt +35 -0
- package/kotlin/README.md +13 -3
- package/kotlin/Sources/android/os/Build.kt +17 -0
- package/kotlin/Sources/android/os/Handler.kt +35 -0
- package/kotlin/Sources/android/os/Looper.kt +17 -0
- package/kotlin/Sources/android/os/MessageQueue.kt +56 -0
- package/kotlin/Sources/android/os/SystemClock.kt +14 -0
- package/kotlin/Sources/android/util/Log.kt +64 -0
- package/kotlin/Sources/java/security/MessageDigest.kt +114 -0
- package/kotlin/Sources/kotlin/text/StringFormat.kt +9 -0
- package/kotlin/Sources/org/json/JSON.kt +44 -0
- package/kotlin/fixtures/counter/Counter.d.kt.ts +1 -0
- package/kotlin/fixtures/counter/Counter.kt +10 -3
- package/kotlin/fixtures/counter/CounterApp.native.tsx +1 -1
- package/kotlin/fixtures/counter/vite.config.ts +2 -2
- package/kotlin/fixtures/headless-counter/Headless.kt +7 -6
- package/kotlin/fixtures/import-animation/Animation.d.kt.ts +7 -0
- package/kotlin/fixtures/import-animation/Animation.kt +80 -0
- package/kotlin/fixtures/import-animation/ImportAnimation.native.tsx +13 -0
- package/kotlin/fixtures/import-animation/README.md +24 -0
- package/kotlin/fixtures/import-animation/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-animation/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-animation/app/routes.d.ts +18 -0
- package/kotlin/fixtures/import-animation/package.json +23 -0
- package/kotlin/fixtures/import-animation/tsconfig.json +20 -0
- package/kotlin/fixtures/import-animation/vite.config.ts +20 -0
- package/kotlin/fixtures/import-editor/Editor.d.kt.ts +7 -0
- package/kotlin/fixtures/import-editor/Editor.kt +59 -0
- package/kotlin/fixtures/import-editor/ImportEditor.native.tsx +23 -0
- package/kotlin/fixtures/import-editor/README.md +27 -0
- package/kotlin/fixtures/import-editor/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-editor/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-editor/package.json +23 -0
- package/kotlin/fixtures/import-editor/tsconfig.json +20 -0
- package/kotlin/fixtures/import-editor/vite.config.ts +20 -0
- package/kotlin/fixtures/import-effects/Effects.d.kt.ts +7 -0
- package/kotlin/fixtures/import-effects/Effects.kt +87 -0
- package/kotlin/fixtures/import-effects/ImportEffects.native.tsx +13 -0
- package/kotlin/fixtures/import-effects/README.md +19 -0
- package/kotlin/fixtures/import-effects/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-effects/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-effects/package.json +23 -0
- package/kotlin/fixtures/import-effects/tsconfig.json +20 -0
- package/kotlin/fixtures/import-effects/vite.config.ts +20 -0
- package/kotlin/fixtures/import-layout/ImportLayout.native.tsx +13 -0
- package/kotlin/fixtures/import-layout/Layout.d.kt.ts +7 -0
- package/kotlin/fixtures/import-layout/Layout.kt +72 -0
- package/kotlin/fixtures/import-layout/README.md +24 -0
- package/kotlin/fixtures/import-layout/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-layout/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-layout/package.json +23 -0
- package/kotlin/fixtures/import-layout/tsconfig.json +20 -0
- package/kotlin/fixtures/import-layout/vite.config.ts +20 -0
- package/kotlin/fixtures/import-navigation/ImportNavigation.native.tsx +18 -0
- package/kotlin/fixtures/import-navigation/Navigation.d.kt.ts +7 -0
- package/kotlin/fixtures/import-navigation/Navigation.kt +104 -0
- package/kotlin/fixtures/import-navigation/README.md +31 -0
- package/kotlin/fixtures/import-navigation/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-navigation/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-navigation/package.json +23 -0
- package/kotlin/fixtures/import-navigation/tsconfig.json +20 -0
- package/kotlin/fixtures/import-navigation/vite.config.ts +20 -0
- package/kotlin/fixtures/import-overlays/ImportOverlays.native.tsx +29 -0
- package/kotlin/fixtures/import-overlays/Overlays.d.kt.ts +7 -0
- package/kotlin/fixtures/import-overlays/Overlays.kt +93 -0
- package/kotlin/fixtures/import-overlays/README.md +34 -0
- package/kotlin/fixtures/import-overlays/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-overlays/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-overlays/package.json +23 -0
- package/kotlin/fixtures/import-overlays/tsconfig.json +20 -0
- package/kotlin/fixtures/import-overlays/vite.config.ts +20 -0
- package/kotlin/fixtures/import-scroll/ImportScroll.native.tsx +13 -0
- package/kotlin/fixtures/import-scroll/README.md +23 -0
- package/kotlin/fixtures/import-scroll/Scroll.d.kt.ts +7 -0
- package/kotlin/fixtures/import-scroll/Scroll.kt +91 -0
- package/kotlin/fixtures/import-scroll/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-scroll/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-scroll/package.json +23 -0
- package/kotlin/fixtures/import-scroll/tsconfig.json +20 -0
- package/kotlin/fixtures/import-scroll/vite.config.ts +20 -0
- package/kotlin/fixtures/import-selection/ImportSelection.native.tsx +44 -0
- package/kotlin/fixtures/import-selection/README.md +32 -0
- package/kotlin/fixtures/import-selection/Selection.d.kt.ts +8 -0
- package/kotlin/fixtures/import-selection/Selection.kt +78 -0
- package/kotlin/fixtures/import-selection/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-selection/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-selection/package.json +23 -0
- package/kotlin/fixtures/import-selection/tsconfig.json +20 -0
- package/kotlin/fixtures/import-selection/vite.config.ts +20 -0
- package/kotlin/fixtures/import-surfaces/ImportSurfaces.native.tsx +13 -0
- package/kotlin/fixtures/import-surfaces/README.md +22 -0
- package/kotlin/fixtures/import-surfaces/Surfaces.d.kt.ts +7 -0
- package/kotlin/fixtures/import-surfaces/Surfaces.kt +64 -0
- package/kotlin/fixtures/import-surfaces/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-surfaces/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-surfaces/package.json +23 -0
- package/kotlin/fixtures/import-surfaces/tsconfig.json +20 -0
- package/kotlin/fixtures/import-surfaces/vite.config.ts +20 -0
- package/kotlin/fixtures/import-values/ImportValues.native.tsx +35 -0
- package/kotlin/fixtures/import-values/README.md +23 -0
- package/kotlin/fixtures/import-values/Values.d.kt.ts +11 -0
- package/kotlin/fixtures/import-values/Values.kt +66 -0
- package/kotlin/fixtures/import-values/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-values/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-values/package.json +23 -0
- package/kotlin/fixtures/import-values/tsconfig.json +20 -0
- package/kotlin/fixtures/import-values/vite.config.ts +20 -0
- package/kotlin/fixtures/kotlin-app-variants/BarBehavior.kt +38 -0
- package/kotlin/fixtures/kotlin-app-variants/BarVisibility.kt +36 -0
- package/kotlin/fixtures/kotlin-app-variants/Ime.kt +3 -1
- package/kotlin/fixtures/kotlin-app-variants/TransientInsets.kt +50 -0
- package/kotlin/fork/NOTICE +6 -1
- package/kotlin/fork/README.md +15 -7
- package/kotlin/fork/build.sh +2 -0
- package/kotlin/fork/compose-worker-globals.patch +186 -4
- package/kotlin/fork/fork.json +2 -2
- package/kotlin/fork/skiko-worker-clock.patch +12 -0
- package/kotlin/toolchain.json +12 -12
- package/kotlin/tools/expo-types.main.kts +115 -0
- package/package.json +17 -23
- package/scripts/dev-server-scanner.ts +92 -66
- package/skills/contrast/SKILL.md +2 -2
- package/skills/peach-debug/SKILL.md +1 -1
- package/skills/peach-perf/SKILL.md +1 -1
- package/skills/peach-setup/SKILL.md +2 -1
- package/skills/peach-test/SKILL.md +8 -0
- package/src/assetCatalogContract.ts +43 -1
- package/src/bridge-contract-input.ts +46 -19
- package/src/bridge-contract.ts +0 -1
- package/src/bundle-channel.mjs +197 -84
- package/src/bundleGlobalFooter.ts +38 -0
- package/src/capture-contract.ts +107 -33
- package/src/cloud-contract.ts +5 -1
- package/src/cloud.ts +5 -2
- package/src/config.ts +22 -3
- package/src/configured-bundle-producer.ts +1 -1
- package/src/desktop-host-delivery.ts +95 -1
- package/src/expo-kotlin-bundle.ts +29 -0
- package/src/expo-kotlin-host.ts +201 -0
- package/src/expo-swift-host.ts +96 -76
- package/src/expoKotlinContract.ts +70 -0
- package/src/expoSwiftContract.ts +4 -25
- package/src/home-paths.ts +7 -4
- package/src/host/bridge-host.ts +23 -3
- package/src/host/fetch-proxy-handler.ts +13 -0
- package/src/host/fetch-proxy-overrides.ts +11 -0
- package/src/host/replacement-module-handler.ts +5 -2
- package/src/index.ts +3 -1
- package/src/kotlin-host.ts +463 -19
- package/src/metro-plugin.ts +56 -28
- package/src/metro-production-bundle.ts +2 -2
- package/src/native-app-identity-project.ts +190 -0
- package/src/native-app-identity.ts +398 -34
- package/src/native-firebase-config.ts +69 -15
- package/src/native-seam-manifest.ts +6 -0
- package/src/one-widget-discovery.ts +2 -1
- package/src/openstep-plist.ts +107 -0
- package/src/react-native-host-modules.ts +4 -0
- package/src/runtime-assets.ts +2 -2
- package/src/sim-client.ts +2 -2
- package/src/swift-binary-modules.ts +3 -1
- package/src/swift-compile.ts +286 -38
- package/src/swift-distribution-contract.ts +4 -0
- package/src/swift-distribution.lock.json +123 -12
- package/src/swift-distribution.ts +7 -3
- package/src/swift-host.ts +298 -83
- package/src/swift-package-manifest.ts +29 -2
- package/src/swift-package.ts +180 -10
- package/src/swift-packages.ts +4 -0
- package/src/swift-standin-emit.ts +4 -1
- package/src/swift-standins.ts +274 -15
- package/src/swift-submit.ts +62 -7
- package/src/swift-toolchain.ts +6 -1
- package/src/swiftContract.ts +126 -3
- package/src/vite-plugin-one.ts +13 -16
- package/src/vite-plugin-swift.ts +4 -5
- package/src/vite-plugin.ts +360 -144
- package/src/vite.d.ts +1 -1
- package/src/worker-system-chunks.ts +221 -0
- package/src/worklet-executable.ts +348 -38
- package/src/worklets-babel.ts +1 -1
- package/src/xcode-project.ts +243 -127
- package/dist-lib/agent-identity.cjs +0 -39
- package/dist-lib/agent-sessions.cjs +0 -760
- package/dist-lib/cli-constants.cjs +0 -32
- package/dist-lib/render-mode.cjs +0 -44
- package/kotlin/AppSources/android/view/View.kt +0 -74
- package/kotlin/AppSources/android/widget/FrameLayout.kt +0 -6
- package/src/render-mode.ts +0 -15
- /package/kotlin/{AppSources → AndroidViewSources}/android/content/Context.kt +0 -0
- /package/kotlin/{AppSources → AndroidViewSources}/android/content/res/Configuration.kt +0 -0
- /package/kotlin/{AppSources → AndroidViewSources}/android/graphics/Color.kt +0 -0
- /package/kotlin/{AppSources → AndroidViewSources}/android/util/DisplayMetrics.kt +0 -0
- /package/kotlin/{AppSources → AndroidViewSources}/dev/peach/app/JavaFormat.kt +0 -0
- /package/kotlin/{AppSources → Sources}/java/lang/Runnable.kt +0 -0
package/src/bundle-channel.mjs
CHANGED
|
@@ -2,11 +2,33 @@
|
|
|
2
2
|
import { createHash } from 'node:crypto'
|
|
3
3
|
import { Script } from 'node:vm'
|
|
4
4
|
import { terminateProcessGroup } from '../../contrast/src/runtime/process-group'
|
|
5
|
+
import { isLoopbackHost } from './backend-origin'
|
|
6
|
+
import { insertExpoKotlinFooter } from './expoKotlinContract'
|
|
5
7
|
import { insertExpoSwiftFooter } from './expoSwiftContract'
|
|
6
8
|
|
|
7
9
|
const digest = (bytes) => createHash('sha256').update(bytes).digest('hex')
|
|
8
10
|
const segment = /^[a-zA-Z0-9._-]+$/
|
|
9
11
|
|
|
12
|
+
// where a dev server asset location's files live in a release. metro names a
|
|
13
|
+
// file outside the project root by an `unstable_path` query.
|
|
14
|
+
function packagedAssetDirectory(location) {
|
|
15
|
+
const parsed = new URL(location, 'https://packaged.invalid')
|
|
16
|
+
if (parsed.origin !== 'https://packaged.invalid')
|
|
17
|
+
throw new Error(`non-packaged asset location: ${location}`)
|
|
18
|
+
const outside = parsed.searchParams.get('unstable_path')
|
|
19
|
+
if (outside === null) return parsed.pathname.replace(/^\/+/, '')
|
|
20
|
+
return `assets/${outside.replace(/^\.\//, '').replace(/\.\.\//g, '_')}`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// each platform publishes its own channel. ios keeps the original deployed
|
|
24
|
+
// keys, which home screen configs already name.
|
|
25
|
+
const channelKey = (platform) =>
|
|
26
|
+
platform === 'ios' ? 'peach/channels/prod' : `peach/channels/${platform}/prod`
|
|
27
|
+
const nativeAssetPath = {
|
|
28
|
+
ios: /^__peach\/(swift|swift-icu)\/[a-f0-9]{64}(\.wasm)?$/,
|
|
29
|
+
android: /^__peach\/kotlin\/[a-f0-9]+\/[A-Za-z0-9._-]+\.(mjs|wasm)$/,
|
|
30
|
+
}
|
|
31
|
+
|
|
10
32
|
export function prepareBundleChannel({
|
|
11
33
|
prefix,
|
|
12
34
|
publicOrigin,
|
|
@@ -14,16 +36,18 @@ export function prepareBundleChannel({
|
|
|
14
36
|
runtimeVersion,
|
|
15
37
|
name,
|
|
16
38
|
icon,
|
|
17
|
-
|
|
18
|
-
assets,
|
|
19
|
-
expoSwift = null,
|
|
39
|
+
platforms,
|
|
20
40
|
}) {
|
|
21
41
|
if (!/^[a-f0-9]{40}$/.test(sourceSha) || !/^\d+$/.test(runtimeVersion)) {
|
|
22
42
|
throw new Error('invalid bundle channel source identity')
|
|
23
43
|
}
|
|
24
44
|
const origin = new URL(publicOrigin)
|
|
25
45
|
if (
|
|
26
|
-
origin
|
|
46
|
+
// a loopback origin serves local release proofs
|
|
47
|
+
!(
|
|
48
|
+
origin.protocol === 'https:' ||
|
|
49
|
+
(origin.protocol === 'http:' && isLoopbackHost(origin.hostname))
|
|
50
|
+
) ||
|
|
27
51
|
origin.pathname !== '/' ||
|
|
28
52
|
origin.search ||
|
|
29
53
|
origin.hash ||
|
|
@@ -37,6 +61,40 @@ export function prepareBundleChannel({
|
|
|
37
61
|
}
|
|
38
62
|
if (!icon.subarray(0, 8).equals(Buffer.from('89504e470d0a1a0a', 'hex')))
|
|
39
63
|
throw new Error('bundle channel icon must be PNG')
|
|
64
|
+
if (!platforms.length) throw new Error('bundle channel has no platforms')
|
|
65
|
+
if (new Set(platforms.map((p) => p.platform)).size !== platforms.length)
|
|
66
|
+
throw new Error('bundle channel repeats a platform')
|
|
67
|
+
const channels = platforms.map((snapshot) =>
|
|
68
|
+
prepareChannel({
|
|
69
|
+
prefix,
|
|
70
|
+
origin,
|
|
71
|
+
sourceSha,
|
|
72
|
+
runtimeVersion,
|
|
73
|
+
name,
|
|
74
|
+
icon,
|
|
75
|
+
...snapshot,
|
|
76
|
+
}),
|
|
77
|
+
)
|
|
78
|
+
return { prefix, origin: origin.origin, channels }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function prepareChannel({
|
|
82
|
+
prefix,
|
|
83
|
+
origin,
|
|
84
|
+
sourceSha,
|
|
85
|
+
runtimeVersion,
|
|
86
|
+
name,
|
|
87
|
+
icon,
|
|
88
|
+
platform,
|
|
89
|
+
bundle,
|
|
90
|
+
assets,
|
|
91
|
+
expoSwift = null,
|
|
92
|
+
expoKotlin = null,
|
|
93
|
+
}) {
|
|
94
|
+
if (!(platform in nativeAssetPath)) throw new Error(`unknown platform: ${platform}`)
|
|
95
|
+
const native = platform === 'ios' ? expoSwift : expoKotlin
|
|
96
|
+
if ((platform === 'ios' ? expoKotlin : expoSwift) !== null)
|
|
97
|
+
throw new Error(`${platform} bundle carries another platform's native modules`)
|
|
40
98
|
const files = new Map()
|
|
41
99
|
for (const asset of assets) {
|
|
42
100
|
if (
|
|
@@ -50,9 +108,9 @@ export function prepareBundleChannel({
|
|
|
50
108
|
if (files.has(asset.path)) throw new Error(`duplicate packaged asset: ${asset.path}`)
|
|
51
109
|
files.set(asset.path, asset.bytes)
|
|
52
110
|
}
|
|
53
|
-
for (const asset of
|
|
54
|
-
if (
|
|
55
|
-
throw new Error(`invalid
|
|
111
|
+
for (const asset of native?.assets ?? []) {
|
|
112
|
+
if (!nativeAssetPath[platform].test(asset.path))
|
|
113
|
+
throw new Error(`invalid native artifact path: ${asset.path}`)
|
|
56
114
|
if (files.has(asset.path)) throw new Error(`duplicate packaged asset: ${asset.path}`)
|
|
57
115
|
files.set(asset.path, asset.bytes)
|
|
58
116
|
}
|
|
@@ -64,7 +122,7 @@ export function prepareBundleChannel({
|
|
|
64
122
|
)) {
|
|
65
123
|
identity.update(JSON.stringify([path, digest(bytes)]))
|
|
66
124
|
}
|
|
67
|
-
if (
|
|
125
|
+
if (native) identity.update(JSON.stringify(native.metadata))
|
|
68
126
|
const releaseKey = `${prefix}/peach/releases/${identity.digest('hex')}`
|
|
69
127
|
const assetBaseUrl = `${origin.origin}/${releaseKey}/`
|
|
70
128
|
const directories = new Set(
|
|
@@ -76,12 +134,7 @@ export function prepareBundleChannel({
|
|
|
76
134
|
/("?httpServerLocation"?\s*:\s*)(?:"((?:[^"\\]|\\.)*)"|`([^`\\$]*)`)/g,
|
|
77
135
|
(match, property, quoted, template) => {
|
|
78
136
|
const location = quoted === undefined ? template : JSON.parse(`"${quoted}"`)
|
|
79
|
-
const
|
|
80
|
-
if (parsed.origin !== 'https://packaged.invalid')
|
|
81
|
-
throw new Error(`non-packaged asset location: ${location}`)
|
|
82
|
-
const path = (parsed.searchParams.get('unstable_path') ?? parsed.pathname)
|
|
83
|
-
.replace(/^\/+/, '')
|
|
84
|
-
.replace(/\.\.\//g, '_')
|
|
137
|
+
const path = packagedAssetDirectory(location)
|
|
85
138
|
if (!directories.has(path)) {
|
|
86
139
|
throw new Error(`bundle asset location has no packaged files: ${location}`)
|
|
87
140
|
}
|
|
@@ -91,25 +144,38 @@ export function prepareBundleChannel({
|
|
|
91
144
|
)
|
|
92
145
|
if (assets.length && !locations)
|
|
93
146
|
throw new Error('bundle has packaged files but no asset metadata')
|
|
147
|
+
const release = (url) => new URL(url.replace(/^\//, ''), assetBaseUrl).href
|
|
94
148
|
let nativeMetadata = null
|
|
95
149
|
if (expoSwift) {
|
|
96
150
|
nativeMetadata = {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
151
|
+
expoSwift: {
|
|
152
|
+
...expoSwift.metadata,
|
|
153
|
+
modules: expoSwift.metadata.modules.map((artifact) => ({
|
|
154
|
+
...artifact,
|
|
155
|
+
url: release(artifact.url),
|
|
156
|
+
icuUrl: artifact.icuUrl.startsWith('/')
|
|
157
|
+
? release(artifact.icuUrl)
|
|
158
|
+
: artifact.icuUrl,
|
|
159
|
+
})),
|
|
160
|
+
},
|
|
105
161
|
}
|
|
106
|
-
source = insertExpoSwiftFooter(source, nativeMetadata)
|
|
162
|
+
source = insertExpoSwiftFooter(source, nativeMetadata.expoSwift)
|
|
163
|
+
}
|
|
164
|
+
if (expoKotlin) {
|
|
165
|
+
const { module } = expoKotlin.metadata
|
|
166
|
+
nativeMetadata = {
|
|
167
|
+
expoKotlin: {
|
|
168
|
+
...expoKotlin.metadata,
|
|
169
|
+
module: module && { ...module, url: release(module.url) },
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
source = insertExpoKotlinFooter(source, nativeMetadata.expoKotlin)
|
|
107
173
|
}
|
|
108
174
|
new Script(source, { filename: 'peach-release-bundle.js' })
|
|
109
175
|
const bundleBytes = Buffer.from(source)
|
|
110
176
|
const metadata = {
|
|
111
177
|
schemaVersion: 1,
|
|
112
|
-
...
|
|
178
|
+
...nativeMetadata,
|
|
113
179
|
channel: 'prod',
|
|
114
180
|
name,
|
|
115
181
|
iconUrl: `${assetBaseUrl}icon.png`,
|
|
@@ -117,7 +183,7 @@ export function prepareBundleChannel({
|
|
|
117
183
|
assetBaseUrl,
|
|
118
184
|
sourceSha,
|
|
119
185
|
runtimeVersion,
|
|
120
|
-
platform
|
|
186
|
+
platform,
|
|
121
187
|
bundleHash: digest(bundleBytes),
|
|
122
188
|
}
|
|
123
189
|
const objects = [
|
|
@@ -131,7 +197,7 @@ export function prepareBundleChannel({
|
|
|
131
197
|
key: `${releaseKey}/${path}`,
|
|
132
198
|
bytes,
|
|
133
199
|
contentType:
|
|
134
|
-
|
|
200
|
+
native?.assets.find((asset) => asset.path === path)?.contentType ??
|
|
135
201
|
{
|
|
136
202
|
png: 'image/png',
|
|
137
203
|
jpg: 'image/jpeg',
|
|
@@ -145,11 +211,11 @@ export function prepareBundleChannel({
|
|
|
145
211
|
'application/octet-stream',
|
|
146
212
|
})),
|
|
147
213
|
]
|
|
148
|
-
return {
|
|
214
|
+
return { platform, key: `${prefix}/${channelKey(platform)}`, metadata, objects }
|
|
149
215
|
}
|
|
150
216
|
|
|
151
217
|
export async function publishBundleChannel(release, { put, fetchObject = fetch }) {
|
|
152
|
-
// prove every immutable object is downloadable before changing
|
|
218
|
+
// prove every immutable object is downloadable before changing a channel.
|
|
153
219
|
async function uploadVerified(object, cacheControl) {
|
|
154
220
|
await put(object.key, object.bytes, object.contentType, cacheControl)
|
|
155
221
|
const response = await fetchObject(`${release.origin}/${object.key}`)
|
|
@@ -159,41 +225,110 @@ export async function publishBundleChannel(release, { put, fetchObject = fetch }
|
|
|
159
225
|
if (digest(bytes) !== digest(object.bytes))
|
|
160
226
|
throw new Error(`bundle channel download mismatch: ${object.key}`)
|
|
161
227
|
}
|
|
162
|
-
for (const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
{ ...
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
228
|
+
for (const channel of release.channels)
|
|
229
|
+
for (const object of channel.objects) await uploadVerified(object)
|
|
230
|
+
for (const channel of release.channels) {
|
|
231
|
+
await uploadVerified({ ...channel.objects[0], key: `${channel.key}.js` }, 'no-store')
|
|
232
|
+
await put(
|
|
233
|
+
`${channel.key}.json`,
|
|
234
|
+
Buffer.from(JSON.stringify(channel.metadata)),
|
|
235
|
+
'application/json',
|
|
236
|
+
'no-store',
|
|
237
|
+
)
|
|
238
|
+
const response = await fetchObject(`${release.origin}/${channel.key}.json`, {
|
|
239
|
+
cache: 'no-store',
|
|
240
|
+
})
|
|
241
|
+
if (
|
|
242
|
+
!response.ok ||
|
|
243
|
+
JSON.stringify(await response.json()) !== JSON.stringify(channel.metadata)
|
|
244
|
+
) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`published ${channel.platform} bundle channel metadata did not match`,
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
console.log(
|
|
250
|
+
`[ota] Peach ${channel.platform} prod channel: ${release.origin}/${channel.key}.json`,
|
|
251
|
+
)
|
|
183
252
|
}
|
|
184
|
-
console.log(
|
|
185
|
-
`[ota] Peach prod channel: ${release.origin}/${release.prefix}/peach/channels/prod.json`,
|
|
186
|
-
)
|
|
187
253
|
}
|
|
188
254
|
|
|
189
|
-
// peach boots
|
|
190
|
-
//
|
|
191
|
-
// from a
|
|
255
|
+
// peach boots an app from the dev server's bundle: it carries the module graph
|
|
256
|
+
// peach maps native seams by, which a release build erases. snapshot each
|
|
257
|
+
// platform's bundle from a running dev server and package the files its
|
|
258
|
+
// assets register, with the app's local Expo native modules for that platform.
|
|
259
|
+
export async function snapshotDevServerBundles(
|
|
260
|
+
origin,
|
|
261
|
+
projectRoot,
|
|
262
|
+
{ platforms = ['ios', 'android'], assertRunning = () => {} } = {},
|
|
263
|
+
) {
|
|
264
|
+
const { readFileSync } = await import('node:fs')
|
|
265
|
+
const { basename } = await import('node:path')
|
|
266
|
+
const snapshots = []
|
|
267
|
+
for (const platform of platforms) {
|
|
268
|
+
const url = `${origin}/index.bundle?platform=${platform}&dev=true&minify=false`
|
|
269
|
+
const deadline = Date.now() + 10 * 60_000
|
|
270
|
+
let bundle
|
|
271
|
+
while (!bundle) {
|
|
272
|
+
assertRunning()
|
|
273
|
+
if (Date.now() > deadline)
|
|
274
|
+
throw new Error(`dev server ${platform} bundle timed out`)
|
|
275
|
+
const response = await fetch(url).catch(() => null)
|
|
276
|
+
if (response?.ok) bundle = Buffer.from(await response.arrayBuffer())
|
|
277
|
+
else await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
278
|
+
}
|
|
279
|
+
const assets = new Map()
|
|
280
|
+
for (const [registration] of bundle
|
|
281
|
+
.toString('utf8')
|
|
282
|
+
.matchAll(/(?<=registerAsset\()\{[^{}]*"__packager_asset": true[^{}]*\}/g)) {
|
|
283
|
+
const asset = JSON.parse(registration)
|
|
284
|
+
const directory = packagedAssetDirectory(asset.httpServerLocation)
|
|
285
|
+
if (asset.files) {
|
|
286
|
+
for (const file of asset.files) {
|
|
287
|
+
const path = `${directory}/${basename(file)}`
|
|
288
|
+
if (!assets.has(path)) assets.set(path, readFileSync(file))
|
|
289
|
+
}
|
|
290
|
+
continue
|
|
291
|
+
}
|
|
292
|
+
// without local files, each scale downloads at the url react native's
|
|
293
|
+
// AssetSourceResolver builds for it
|
|
294
|
+
for (const scale of asset.scales) {
|
|
295
|
+
const file = `${asset.name}${scale === 1 ? '' : `@${scale}x`}.${asset.type}`
|
|
296
|
+
const path = `${directory}/${file}`
|
|
297
|
+
if (assets.has(path)) continue
|
|
298
|
+
const response = await fetch(
|
|
299
|
+
`${origin}${asset.httpServerLocation}/${file}?platform=${platform}&hash=${asset.hash}`,
|
|
300
|
+
)
|
|
301
|
+
if (!response.ok)
|
|
302
|
+
throw new Error(`dev server asset ${path} failed: ${response.status}`)
|
|
303
|
+
assets.set(path, Buffer.from(await response.arrayBuffer()))
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
snapshots.push({
|
|
307
|
+
platform,
|
|
308
|
+
bundle,
|
|
309
|
+
assets: [...assets].map(([path, bytes]) => ({ path, bytes })),
|
|
310
|
+
...(platform === 'ios'
|
|
311
|
+
? {
|
|
312
|
+
expoSwift: await (
|
|
313
|
+
await import('./expo-swift-bundle')
|
|
314
|
+
).buildExpoSwiftBundle(projectRoot),
|
|
315
|
+
}
|
|
316
|
+
: {
|
|
317
|
+
expoKotlin: await (
|
|
318
|
+
await import('./expo-kotlin-bundle')
|
|
319
|
+
).buildExpoKotlinBundle(projectRoot),
|
|
320
|
+
}),
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
return { platforms: snapshots }
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// a One app's bundles, from a private dev server
|
|
192
327
|
export async function snapshotOneDevBundle(projectRoot, env = {}) {
|
|
193
328
|
const { spawn } = await import('node:child_process')
|
|
194
329
|
const { readFileSync } = await import('node:fs')
|
|
195
330
|
const { createServer } = await import('node:net')
|
|
196
|
-
const {
|
|
331
|
+
const { dirname, join } = await import('node:path')
|
|
197
332
|
const { createRequire } = await import('node:module')
|
|
198
333
|
const onePackage = createRequire(join(projectRoot, 'package.json')).resolve(
|
|
199
334
|
'one/package.json',
|
|
@@ -217,34 +352,12 @@ export async function snapshotOneDevBundle(projectRoot, env = {}) {
|
|
|
217
352
|
detached: true,
|
|
218
353
|
})
|
|
219
354
|
try {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (Date.now() > deadline) throw new Error('one dev bundle timed out')
|
|
227
|
-
const response = await fetch(url).catch(() => null)
|
|
228
|
-
if (response?.ok) bundle = Buffer.from(await response.arrayBuffer())
|
|
229
|
-
else await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
230
|
-
}
|
|
231
|
-
const assets = new Map()
|
|
232
|
-
for (const [registration] of bundle
|
|
233
|
-
.toString('utf8')
|
|
234
|
-
.matchAll(/(?<=registerAsset\()\{[^{}]*"__packager_asset": true[^{}]*\}/g)) {
|
|
235
|
-
const asset = JSON.parse(registration)
|
|
236
|
-
for (const file of asset.files) {
|
|
237
|
-
const path = `${asset.httpServerLocation.replace(/^\/+/, '')}/${basename(file)}`
|
|
238
|
-
if (!assets.has(path)) assets.set(path, readFileSync(file))
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
const { buildExpoSwiftBundle } = await import('./expo-swift-bundle')
|
|
242
|
-
const expoSwift = await buildExpoSwiftBundle(projectRoot)
|
|
243
|
-
return {
|
|
244
|
-
bundle,
|
|
245
|
-
assets: [...assets].map(([path, bytes]) => ({ path, bytes })),
|
|
246
|
-
expoSwift,
|
|
247
|
-
}
|
|
355
|
+
return await snapshotDevServerBundles(`http://127.0.0.1:${port}`, projectRoot, {
|
|
356
|
+
assertRunning: () => {
|
|
357
|
+
if (child.exitCode !== null)
|
|
358
|
+
throw new Error(`one dev exited with ${child.exitCode}`)
|
|
359
|
+
},
|
|
360
|
+
})
|
|
248
361
|
} finally {
|
|
249
362
|
await terminateProcessGroup(child)
|
|
250
363
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// one `globalThis.<name>=<json>;` line a release publisher appends to a bundle,
|
|
2
|
+
// which boot analysis reads before evaluating it. it sits before the module
|
|
3
|
+
// path identity footer, which must stay the bundle's last line.
|
|
4
|
+
|
|
5
|
+
export function readBundleGlobalFooter(source: string, name: string): unknown {
|
|
6
|
+
const prefix = `globalThis.${name}=`
|
|
7
|
+
const start = source.lastIndexOf(`\n${prefix}`)
|
|
8
|
+
if (start < 0) return null
|
|
9
|
+
const end = source.indexOf('\n', start + 1)
|
|
10
|
+
const line = source.slice(start + 1, end < 0 ? undefined : end)
|
|
11
|
+
if (!line.endsWith(';')) throw new Error(`${name} footer is truncated`)
|
|
12
|
+
const value: unknown = JSON.parse(line.slice(prefix.length, -1))
|
|
13
|
+
if (value === null) throw new Error(`${name} footer has no metadata`)
|
|
14
|
+
return value
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function insertBundleGlobalFooter(
|
|
18
|
+
source: string,
|
|
19
|
+
name: string,
|
|
20
|
+
value: unknown,
|
|
21
|
+
): string {
|
|
22
|
+
const prefix = `globalThis.${name}=`
|
|
23
|
+
const start = source.lastIndexOf(`\n${prefix}`)
|
|
24
|
+
if (start >= 0) {
|
|
25
|
+
readBundleGlobalFooter(source, name)
|
|
26
|
+
const end = source.indexOf('\n', start + 1)
|
|
27
|
+
source = source.slice(0, start) + (end < 0 ? '' : source.slice(end))
|
|
28
|
+
}
|
|
29
|
+
const json = JSON.stringify(value)
|
|
30
|
+
.replace(/\u2028/g, '\\u2028')
|
|
31
|
+
.replace(/\u2029/g, '\\u2029')
|
|
32
|
+
.replace(/<\//g, '<\\/')
|
|
33
|
+
const footer = `\n${prefix}${json};`
|
|
34
|
+
const identity = source.lastIndexOf('\nglobalThis.__sootsimModulePaths=')
|
|
35
|
+
return identity < 0
|
|
36
|
+
? source + footer
|
|
37
|
+
: source.slice(0, identity) + footer + source.slice(identity)
|
|
38
|
+
}
|
package/src/capture-contract.ts
CHANGED
|
@@ -155,6 +155,8 @@ export interface RnxCaptureRegionRequest {
|
|
|
155
155
|
/** host node whose own subtree is rendered into this transparent atlas region */
|
|
156
156
|
nodeId: number
|
|
157
157
|
frame: RnxCaptureRect
|
|
158
|
+
/** the region holds the node's own layer only; its children replay over it */
|
|
159
|
+
ownPaint?: true
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
export interface RnxRasterRegionTree {
|
|
@@ -186,22 +188,62 @@ function isLiveRasterSource(node: RnxRasterRegionTree['nodes'][number]): boolean
|
|
|
186
188
|
)
|
|
187
189
|
}
|
|
188
190
|
|
|
189
|
-
|
|
191
|
+
// the frame a raster layer captures, before paint outsets. a live source
|
|
192
|
+
// (video, a gpu scene) is cut to its visible bounds: opaque content in front
|
|
193
|
+
// of it replays semantically over the rest. one hidden entirely keeps its
|
|
194
|
+
// layout frame. the budget and the region both read this, so a layer costs
|
|
195
|
+
// the atlas exactly what it was budgeted.
|
|
196
|
+
function rasterLayerFrame(node: RnxRasterRegionTree['nodes'][number]): RnxCaptureRect {
|
|
197
|
+
const visible = node.visibleFrame
|
|
198
|
+
if (isLiveRasterSource(node) && visible && visible.width > 0 && visible.height > 0) {
|
|
199
|
+
return { x: visible.x, y: visible.y, width: visible.width, height: visible.height }
|
|
200
|
+
}
|
|
201
|
+
return (
|
|
202
|
+
node.frame ?? {
|
|
203
|
+
x: node.absolutePosition.x,
|
|
204
|
+
y: node.absolutePosition.y,
|
|
205
|
+
width: node.layout.width,
|
|
206
|
+
height: node.layout.height,
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// a live source the app renders itself on the gpu (an expo-gl or webgpu view:
|
|
212
|
+
// a game, a 3d scene). its pixels are whatever the app draws next, so design
|
|
213
|
+
// shows a placeholder there instead of photographing a frame.
|
|
214
|
+
export function isGpuSceneSource(node: RnxRasterRegionTree['nodes'][number]): boolean {
|
|
215
|
+
if (!isLiveRasterSource(node)) return false
|
|
216
|
+
const source = node.props?.source
|
|
217
|
+
const uri = source && typeof source === 'object' ? Reflect.get(source, 'uri') : null
|
|
218
|
+
return (
|
|
219
|
+
typeof uri === 'string' &&
|
|
220
|
+
(uri.startsWith('live:expo-gl-') || uri.startsWith('live:wgpu-'))
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// `nodeIds` is every node the atlas carries. a node in `ownPaint` carries only
|
|
225
|
+
// its own layer: glass larger than a layer may hold (a sheet's material) is
|
|
226
|
+
// captured without its children, which stay semantic and paint over it, and
|
|
227
|
+
// the glass controls inside it keep their own regions.
|
|
228
|
+
export type RnxRasterSelection = { nodeIds: Set<number>; ownPaint: Set<number> }
|
|
229
|
+
|
|
230
|
+
export function selectRnxRasterNodeIds(tree: RnxRasterRegionTree): RnxRasterSelection {
|
|
190
231
|
const nodesById = new Map(tree.nodes.map((node) => [node.nodeId, node]))
|
|
191
232
|
const maxLayerArea = tree.width * tree.height * 0.25
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
233
|
+
const eligible = new Map<
|
|
234
|
+
number,
|
|
235
|
+
{ nodeId: number; area: number; liveVideo: boolean; ownPaint: boolean }
|
|
236
|
+
>()
|
|
196
237
|
for (const node of tree.nodes) {
|
|
197
238
|
const liveVideo = isLiveRasterSource(node)
|
|
198
|
-
const visibleFrame =
|
|
199
|
-
|
|
200
|
-
node.visibleFrame
|
|
201
|
-
(node.visibleFrame.width <= 0 || node.visibleFrame.height <= 0)
|
|
202
|
-
? node.frame
|
|
203
|
-
: (node.visibleFrame ?? node.frame)
|
|
239
|
+
const visibleFrame = liveVideo
|
|
240
|
+
? rasterLayerFrame(node)
|
|
241
|
+
: (node.visibleFrame ?? node.frame)
|
|
204
242
|
if (!visibleFrame || visibleFrame.width <= 0 || visibleFrame.height <= 0) continue
|
|
243
|
+
const glass =
|
|
244
|
+
node.style._liquidGlass !== undefined ||
|
|
245
|
+
node.style._liquidGlassEdge !== undefined ||
|
|
246
|
+
node.style._liquidGlassMaterialTint !== undefined
|
|
205
247
|
const unsupported =
|
|
206
248
|
node.type === 'masked-view' ||
|
|
207
249
|
liveVideo ||
|
|
@@ -211,40 +253,58 @@ export function selectRnxRasterNodeIds(tree: RnxRasterRegionTree): Set<number> {
|
|
|
211
253
|
/\uFE0F|\u200D|\u20E3|\p{Emoji_Modifier}|\p{Regional_Indicator}/u.test(
|
|
212
254
|
node.text,
|
|
213
255
|
))) ||
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const area =
|
|
218
|
-
if (
|
|
219
|
-
eligible.set(node.nodeId, { nodeId: node.nodeId, area, liveVideo })
|
|
256
|
+
glass
|
|
257
|
+
if (!unsupported) continue
|
|
258
|
+
const captured = rasterLayerFrame(node)
|
|
259
|
+
const area = captured.width * captured.height
|
|
260
|
+
if (liveVideo || area <= maxLayerArea) {
|
|
261
|
+
eligible.set(node.nodeId, { nodeId: node.nodeId, area, liveVideo, ownPaint: false })
|
|
262
|
+
} else if (glass) {
|
|
263
|
+
eligible.set(node.nodeId, { nodeId: node.nodeId, area, liveVideo, ownPaint: true })
|
|
220
264
|
}
|
|
221
265
|
}
|
|
266
|
+
// a whole-subtree layer absorbs the layers inside it; an own-paint layer
|
|
267
|
+
// leaves them their own regions.
|
|
222
268
|
for (const nodeId of [...eligible.keys()]) {
|
|
223
269
|
let parentNodeId = nodesById.get(nodeId)?.parentNodeId ?? null
|
|
224
270
|
while (parentNodeId !== null) {
|
|
225
|
-
|
|
271
|
+
const parent = eligible.get(parentNodeId)
|
|
272
|
+
if (parent && !parent.ownPaint) {
|
|
226
273
|
eligible.delete(nodeId)
|
|
227
274
|
break
|
|
228
275
|
}
|
|
229
276
|
parentNodeId = nodesById.get(parentNodeId)?.parentNodeId ?? null
|
|
230
277
|
}
|
|
231
278
|
}
|
|
279
|
+
// live video and own-paint glass (a whole sheet's material) claim the atlas
|
|
280
|
+
// first, up to a screen between them; the other layers follow smallest
|
|
281
|
+
// first, up to half a screen of their own. the whole atlas stays within a
|
|
282
|
+
// screen and a quarter, which the packer's pixel budget holds at 3x.
|
|
232
283
|
const candidates = [...eligible.values()].sort(
|
|
233
284
|
(left, right) =>
|
|
234
285
|
Number(right.liveVideo) - Number(left.liveVideo) ||
|
|
286
|
+
Number(right.ownPaint) - Number(left.ownPaint) ||
|
|
235
287
|
left.area - right.area ||
|
|
236
288
|
left.nodeId - right.nodeId,
|
|
237
289
|
)
|
|
238
290
|
const screenArea = tree.width * tree.height
|
|
239
|
-
const
|
|
291
|
+
const selection: RnxRasterSelection = { nodeIds: new Set(), ownPaint: new Set() }
|
|
240
292
|
let atlasArea = 0
|
|
293
|
+
let layerArea = 0
|
|
241
294
|
for (const candidate of candidates) {
|
|
242
|
-
const
|
|
243
|
-
if (
|
|
244
|
-
|
|
295
|
+
const leading = candidate.liveVideo || candidate.ownPaint
|
|
296
|
+
if (
|
|
297
|
+
atlasArea + candidate.area > screenArea * (leading ? 1 : 1.25) ||
|
|
298
|
+
(!leading && layerArea + candidate.area > screenArea * 0.5)
|
|
299
|
+
) {
|
|
300
|
+
continue
|
|
301
|
+
}
|
|
302
|
+
selection.nodeIds.add(candidate.nodeId)
|
|
303
|
+
if (candidate.ownPaint) selection.ownPaint.add(candidate.nodeId)
|
|
245
304
|
atlasArea += candidate.area
|
|
305
|
+
if (!leading) layerArea += candidate.area
|
|
246
306
|
}
|
|
247
|
-
return
|
|
307
|
+
return selection
|
|
248
308
|
}
|
|
249
309
|
|
|
250
310
|
export function rnxRasterFrames(
|
|
@@ -254,12 +314,7 @@ export function rnxRasterFrames(
|
|
|
254
314
|
const frames = new Map<number, RnxCaptureRect>()
|
|
255
315
|
for (const node of tree.nodes) {
|
|
256
316
|
if (!nodeIds.has(node.nodeId)) continue
|
|
257
|
-
const frame = node
|
|
258
|
-
x: node.absolutePosition.x,
|
|
259
|
-
y: node.absolutePosition.y,
|
|
260
|
-
width: node.layout.width,
|
|
261
|
-
height: node.layout.height,
|
|
262
|
-
}
|
|
317
|
+
const frame = rasterLayerFrame(node)
|
|
263
318
|
// a region is what the node paints, and nothing else: a producer that crops
|
|
264
319
|
// a composited screenshot copies every pixel inside it, so any margin past
|
|
265
320
|
// the paint carries neighbours (a header title, a hairline, a list row)
|
|
@@ -371,11 +426,27 @@ export function rnxCaptureRasterRegions(
|
|
|
371
426
|
visibleFrame: node.layout.visibleFrame,
|
|
372
427
|
})),
|
|
373
428
|
}
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
429
|
+
const selection = selectRnxRasterNodeIds(tree)
|
|
430
|
+
return rnxRasterRegionRequests(tree, selection)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// the region requests a producer packs for a selection
|
|
434
|
+
export function rnxRasterRegionRequests(
|
|
435
|
+
tree: RnxRasterRegionTree,
|
|
436
|
+
selection: RnxRasterSelection,
|
|
437
|
+
): RnxCaptureRegionRequest[] {
|
|
438
|
+
const frames = rnxRasterFrames(tree, selection.nodeIds)
|
|
439
|
+
return [...selection.nodeIds].flatMap((nodeId) => {
|
|
377
440
|
const frame = frames.get(nodeId)
|
|
378
|
-
|
|
441
|
+
if (!frame) return []
|
|
442
|
+
return [
|
|
443
|
+
{
|
|
444
|
+
key: String(nodeId),
|
|
445
|
+
nodeId,
|
|
446
|
+
frame,
|
|
447
|
+
...(selection.ownPaint.has(nodeId) ? { ownPaint: true as const } : {}),
|
|
448
|
+
},
|
|
449
|
+
]
|
|
379
450
|
})
|
|
380
451
|
}
|
|
381
452
|
|
|
@@ -445,6 +516,7 @@ export function packRnxCaptureRegions(
|
|
|
445
516
|
return {
|
|
446
517
|
key: region.key,
|
|
447
518
|
nodeId: region.nodeId,
|
|
519
|
+
...(region.ownPaint ? { ownPaint: true as const } : {}),
|
|
448
520
|
frame: {
|
|
449
521
|
x: left / pixelRatio,
|
|
450
522
|
y: top / pixelRatio,
|
|
@@ -514,6 +586,7 @@ export function packRnxCaptureRegions(
|
|
|
514
586
|
atlasRegions: best.regions.map((region) => ({
|
|
515
587
|
key: region.key,
|
|
516
588
|
nodeId: region.nodeId,
|
|
589
|
+
...(region.ownPaint ? { ownPaint: true as const } : {}),
|
|
517
590
|
frame: region.frame,
|
|
518
591
|
// a region flush with the far edge is anchored to the logical edge so
|
|
519
592
|
// its source never runs past the atlas by a rounding hair
|
|
@@ -784,6 +857,7 @@ export function isRnxCaptureRegionAtlas(value: unknown): value is RnxCaptureRegi
|
|
|
784
857
|
keys.has(region.key) ||
|
|
785
858
|
!captureInteger(region.nodeId) ||
|
|
786
859
|
region.nodeId <= 0 ||
|
|
860
|
+
(region.ownPaint !== undefined && region.ownPaint !== true) ||
|
|
787
861
|
!captureRect(region.frame) ||
|
|
788
862
|
region.frame.width <= 0 ||
|
|
789
863
|
region.frame.height <= 0 ||
|
package/src/cloud-contract.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { BridgeCommandType } from './bridge-contract.ts'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// largest guest artifact a nano isolate measurably boots, settles, screenshots,
|
|
4
|
+
// and survives repeated invocations within Cloudflare Dynamic Worker platform
|
|
5
|
+
// limits (67,108,864 bytes total Dynamic Worker size minus 26,347,088 bytes engine
|
|
6
|
+
// harness overhead).
|
|
7
|
+
export const RNX_CLOUD_MAX_ARTIFACT_BYTES = 40_761_776
|
|
4
8
|
export const RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES = 256
|
|
5
9
|
export const RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024
|
|
6
10
|
export const RNX_CLOUD_TEST_FILES_MAX_COUNT = 256
|