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/cloud.ts
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import { isRecord } from './is-record'
|
|
29
29
|
import { rnxPublicBrand } from './public-brand'
|
|
30
30
|
import { sleep } from './sleep'
|
|
31
|
-
import type { SimStateOptions } from './bridge-contract'
|
|
31
|
+
import type { SemanticWaitCondition, SimStateOptions } from './bridge-contract'
|
|
32
32
|
|
|
33
33
|
export {
|
|
34
34
|
isRnxCloudCommandScope,
|
|
@@ -407,7 +407,10 @@ export class RemoteSimulator {
|
|
|
407
407
|
do: (command) => this.sendCommand(command),
|
|
408
408
|
wait: (condition, options) => {
|
|
409
409
|
const timeoutMs = options?.maxMs ?? DEFAULT_WAIT_READY_MS
|
|
410
|
-
const waitCondition
|
|
410
|
+
const waitCondition: SemanticWaitCondition =
|
|
411
|
+
condition === 'ready'
|
|
412
|
+
? { type: 'ready' }
|
|
413
|
+
: { type: 'present', selector: { testID: condition.selector } }
|
|
411
414
|
return this.sendCommand(
|
|
412
415
|
{
|
|
413
416
|
type: 'waitFor',
|
package/src/config.ts
CHANGED
|
@@ -8,6 +8,14 @@ import { isValidRuntimeVersion } from '@contrast/runtime-delivery/version'
|
|
|
8
8
|
import { isRecord } from './is-record'
|
|
9
9
|
import type { RevenueCatTestStoreConfig } from '@peach/compat/revenuecat-test-store'
|
|
10
10
|
|
|
11
|
+
export type NativeRenderer = 'paper' | 'fabric'
|
|
12
|
+
|
|
13
|
+
export function readNativeRenderer(value: unknown): NativeRenderer | undefined {
|
|
14
|
+
if (value === undefined) return undefined
|
|
15
|
+
if (value === 'paper' || value === 'fabric') return value
|
|
16
|
+
throw new Error('nativeRenderer must be paper or fabric')
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
export type ModuleResolution =
|
|
12
20
|
// an empty module under `modules`; under `nativeModules` / `turboModules` a
|
|
13
21
|
// present-but-inert native module whose every method call is a no-op.
|
|
@@ -28,6 +36,8 @@ export type NativeViewResolution = 'noop' | { file: string }
|
|
|
28
36
|
export interface PeachConfig<Settings extends Record<string, any> = Record<string, any>> {
|
|
29
37
|
// host-only engine selection. omitted means the current stable runtime.
|
|
30
38
|
runtimeVersion?: string
|
|
39
|
+
// native host identity. omission preserves the Fabric default.
|
|
40
|
+
nativeRenderer?: NativeRenderer
|
|
31
41
|
modules?: Record<string, ModuleResolution>
|
|
32
42
|
turboModules?: Record<string, NativeModuleResolution>
|
|
33
43
|
nativeModules?: Record<string, NativeModuleResolution>
|
|
@@ -71,9 +81,14 @@ export function collectConfiguredFileModules(config?: PeachConfig): string[] {
|
|
|
71
81
|
]
|
|
72
82
|
}
|
|
73
83
|
|
|
74
|
-
export function isPeachConfig
|
|
84
|
+
export function isPeachConfig<Settings extends Record<string, any> = Record<string, any>>(
|
|
85
|
+
value: unknown,
|
|
86
|
+
): value is PeachConfig<Settings> {
|
|
75
87
|
if (!isRecord(value)) return false
|
|
76
88
|
return (
|
|
89
|
+
(value.nativeRenderer === undefined ||
|
|
90
|
+
value.nativeRenderer === 'paper' ||
|
|
91
|
+
value.nativeRenderer === 'fabric') &&
|
|
77
92
|
(value.runtimeVersion === undefined ||
|
|
78
93
|
(typeof value.runtimeVersion === 'string' &&
|
|
79
94
|
value.runtimeVersion === value.runtimeVersion.trim() &&
|
|
@@ -169,6 +184,7 @@ export function hasPeachConfig<
|
|
|
169
184
|
>(config?: PeachConfig<Settings> | null): config is PeachConfig<Settings> {
|
|
170
185
|
if (!config) return false
|
|
171
186
|
return (
|
|
187
|
+
config.nativeRenderer !== undefined ||
|
|
172
188
|
(typeof config.runtimeVersion === 'string' && config.runtimeVersion.length > 0) ||
|
|
173
189
|
hasOwnKeys(config.modules as Record<string, unknown> | undefined) ||
|
|
174
190
|
hasOwnKeys(config.turboModules as Record<string, unknown> | undefined) ||
|
|
@@ -193,6 +209,7 @@ export function mergePeachConfig<
|
|
|
193
209
|
|
|
194
210
|
return {
|
|
195
211
|
runtimeVersion: override.runtimeVersion ?? base.runtimeVersion,
|
|
212
|
+
nativeRenderer: override.nativeRenderer ?? base.nativeRenderer,
|
|
196
213
|
modules: { ...(base.modules || {}), ...(override.modules || {}) },
|
|
197
214
|
turboModules: { ...(base.turboModules || {}), ...(override.turboModules || {}) },
|
|
198
215
|
nativeModules: {
|
|
@@ -217,14 +234,16 @@ export function readPeachConfigFromSearchParams<
|
|
|
217
234
|
>(searchParams: URLSearchParams): PeachConfig<Settings> | undefined {
|
|
218
235
|
const raw = searchParams.get(PEACH_CONFIG_QUERY_PARAM)
|
|
219
236
|
if (!raw) return undefined
|
|
237
|
+
let parsed: unknown
|
|
220
238
|
try {
|
|
221
|
-
|
|
222
|
-
return hasPeachConfig(parsed) ? parsed : undefined
|
|
239
|
+
parsed = JSON.parse(raw)
|
|
223
240
|
} catch (error) {
|
|
224
241
|
const message = error instanceof Error ? error.message : String(error)
|
|
225
242
|
console.warn(`[peach] invalid ${PEACH_CONFIG_QUERY_PARAM}: ${message}`)
|
|
226
243
|
return undefined
|
|
227
244
|
}
|
|
245
|
+
if (isRecord(parsed)) readNativeRenderer(parsed.nativeRenderer)
|
|
246
|
+
return isPeachConfig<Settings>(parsed) && hasPeachConfig(parsed) ? parsed : undefined
|
|
228
247
|
}
|
|
229
248
|
|
|
230
249
|
export function applyPeachConfigToUrl<
|
|
@@ -90,7 +90,7 @@ export async function produceConfiguredBundle(
|
|
|
90
90
|
const compact = await minify(code, {
|
|
91
91
|
compress: false,
|
|
92
92
|
mangle: false,
|
|
93
|
-
format: { comments: false
|
|
93
|
+
format: { comments: false },
|
|
94
94
|
})
|
|
95
95
|
if (!compact.code || /[\n\r]/.test(compact.code))
|
|
96
96
|
throw new Error('configured bundle prefix must preserve the guest source lines')
|
|
@@ -79,6 +79,88 @@ export function isNativeHostBundle(app: string): boolean {
|
|
|
79
79
|
)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** a default production install owns the scheme until uninstall or home purge. */
|
|
83
|
+
export function registerNativeDesktopLinks(): void {
|
|
84
|
+
if (
|
|
85
|
+
desktopHostPlatform() !== 'mac-arm64' ||
|
|
86
|
+
!isNativeHostBundle(MAC_HOST_APP) ||
|
|
87
|
+
bundleValue(MAC_HOST_APP, 'PeachUpdateChannel') !== 'production'
|
|
88
|
+
)
|
|
89
|
+
return
|
|
90
|
+
const receipt = path.join(desktopHostsDir(), 'url-handler.json')
|
|
91
|
+
fs.mkdirSync(desktopHostsDir(), { recursive: true })
|
|
92
|
+
const script = `
|
|
93
|
+
ObjC.import('CoreServices');
|
|
94
|
+
ObjC.import('Foundation');
|
|
95
|
+
ObjC.bindFunction('LSCopyDefaultHandlerForURLScheme', ['id', ['id']]);
|
|
96
|
+
ObjC.bindFunction('LSSetDefaultHandlerForURLScheme', ['int', ['id', 'id']]);
|
|
97
|
+
ObjC.bindFunction('LSCopyAllHandlersForURLScheme', ['id', ['id']]);
|
|
98
|
+
const current = ObjC.unwrap($.LSCopyDefaultHandlerForURLScheme('peach')) || null;
|
|
99
|
+
const handlers = ObjC.deepUnwrap($.LSCopyAllHandlersForURLScheme('peach')) || [];
|
|
100
|
+
const previous = current === ${JSON.stringify(HOST_BUNDLE_ID)}
|
|
101
|
+
? handlers.find(handler => handler !== ${JSON.stringify(HOST_BUNDLE_ID)}) || null : current;
|
|
102
|
+
if (current !== ${JSON.stringify(HOST_BUNDLE_ID)} || ${!fs.existsSync(receipt)}) {
|
|
103
|
+
const data = $.NSString.stringWithString(JSON.stringify({ previous })).dataUsingEncoding($.NSUTF8StringEncoding);
|
|
104
|
+
if (!data.writeToFileAtomically(${JSON.stringify(receipt)}, true))
|
|
105
|
+
throw new Error('native URL handler receipt write failed');
|
|
106
|
+
}
|
|
107
|
+
const registered = $.LSRegisterURL($.NSURL.fileURLWithPath(${JSON.stringify(MAC_HOST_APP)}), true);
|
|
108
|
+
if (registered !== 0) throw new Error('native URL registration failed: ' + registered);
|
|
109
|
+
const selected = $.LSSetDefaultHandlerForURLScheme('peach', ${JSON.stringify(HOST_BUNDLE_ID)});
|
|
110
|
+
if (selected !== 0) throw new Error('native URL handler selection failed: ' + selected);
|
|
111
|
+
if (ObjC.unwrap($.LSCopyDefaultHandlerForURLScheme('peach')) !== ${JSON.stringify(HOST_BUNDLE_ID)})
|
|
112
|
+
throw new Error('native URL handler readback failed');
|
|
113
|
+
`
|
|
114
|
+
// runner-toolchain-platform: darwin
|
|
115
|
+
const result = spawnSync('/usr/bin/osascript', ['-l', 'JavaScript', '-e', script], {
|
|
116
|
+
encoding: 'utf8',
|
|
117
|
+
})
|
|
118
|
+
if (result.status !== 0)
|
|
119
|
+
throw new Error(`native deep-link registration failed: ${result.stderr.trim()}`)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** restore the captured handler only while this install still owns the scheme. */
|
|
123
|
+
export function unregisterNativeDesktopLinks(): void {
|
|
124
|
+
if (desktopHostPlatform() !== 'mac-arm64') return
|
|
125
|
+
const receipt = path.join(desktopHostsDir(), 'url-handler.json')
|
|
126
|
+
if (!fs.existsSync(receipt)) return
|
|
127
|
+
const state: unknown = JSON.parse(fs.readFileSync(receipt, 'utf8'))
|
|
128
|
+
if (
|
|
129
|
+
typeof state !== 'object' ||
|
|
130
|
+
state === null ||
|
|
131
|
+
!('previous' in state) ||
|
|
132
|
+
(state.previous !== null && typeof state.previous !== 'string')
|
|
133
|
+
)
|
|
134
|
+
throw new Error('invalid native URL registration receipt')
|
|
135
|
+
const script = `
|
|
136
|
+
ObjC.import('CoreServices');
|
|
137
|
+
ObjC.bindFunction('LSCopyDefaultHandlerForURLScheme', ['id', ['id']]);
|
|
138
|
+
ObjC.bindFunction('LSSetDefaultHandlerForURLScheme', ['int', ['id', 'id']]);
|
|
139
|
+
const current = ObjC.unwrap($.LSCopyDefaultHandlerForURLScheme('peach'));
|
|
140
|
+
const previous = ${JSON.stringify(state.previous)};
|
|
141
|
+
if (current === ${JSON.stringify(HOST_BUNDLE_ID)} && previous) {
|
|
142
|
+
const status = $.LSSetDefaultHandlerForURLScheme('peach', previous);
|
|
143
|
+
if (status !== 0 || ObjC.unwrap($.LSCopyDefaultHandlerForURLScheme('peach')) !== previous)
|
|
144
|
+
throw new Error('native URL handler restore failed: ' + status);
|
|
145
|
+
}
|
|
146
|
+
`
|
|
147
|
+
// runner-toolchain-platform: darwin
|
|
148
|
+
run('/usr/bin/osascript', ['-l', 'JavaScript', '-e', script])
|
|
149
|
+
// runner-toolchain-platform: darwin
|
|
150
|
+
run(
|
|
151
|
+
'/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister',
|
|
152
|
+
['-u', MAC_HOST_APP],
|
|
153
|
+
)
|
|
154
|
+
fs.unlinkSync(receipt)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function uninstallNativeDesktopHost(): void {
|
|
158
|
+
unregisterNativeDesktopLinks()
|
|
159
|
+
if (desktopHostPlatform() === 'mac-arm64' && isNativeHostBundle(MAC_HOST_APP))
|
|
160
|
+
fs.rmSync(MAC_HOST_APP, { recursive: true, force: true })
|
|
161
|
+
fs.rmSync(desktopHostsDir(), { recursive: true, force: true })
|
|
162
|
+
}
|
|
163
|
+
|
|
82
164
|
function placedHostVersion(): string | null {
|
|
83
165
|
if (desktopHostPlatform() === 'linux-x64') {
|
|
84
166
|
try {
|
|
@@ -105,7 +187,19 @@ export function activeDesktopHostApp(): string | null {
|
|
|
105
187
|
|
|
106
188
|
/** the installed standalone host command; the driver appends its connection arguments. */
|
|
107
189
|
export function headlessHostCommand(): string[] | null {
|
|
108
|
-
|
|
190
|
+
const platform = desktopHostPlatform()
|
|
191
|
+
if (!platform) return null
|
|
192
|
+
if (platform === 'mac-arm64') {
|
|
193
|
+
const app = process.env.PEACH_DESKTOP_APP
|
|
194
|
+
? path.resolve(process.env.PEACH_DESKTOP_APP)
|
|
195
|
+
: activeDesktopHostApp()
|
|
196
|
+
if (!app) return null
|
|
197
|
+
const launcher = path.join(app, 'Contents/Frameworks/node')
|
|
198
|
+
const host = path.join(app, 'Contents/Resources/node/host.mjs')
|
|
199
|
+
// releases before the bundled launcher have no headless host
|
|
200
|
+
if (!fs.existsSync(launcher) || !fs.existsSync(host)) return null
|
|
201
|
+
return [launcher, '--experimental-vm-modules', host]
|
|
202
|
+
}
|
|
109
203
|
const version = placedHostVersion()
|
|
110
204
|
if (!version) return null
|
|
111
205
|
const dir = desktopHostDir(version)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// release publishers use the same app compiler as local Android launches, then
|
|
2
|
+
// carry its immutable Kotlin/Wasm module beside the javascript bundle.
|
|
3
|
+
import { compileExpoKotlinModules } from './expo-kotlin-host'
|
|
4
|
+
import type { ExpoKotlinCompilationResult } from './expoKotlinContract'
|
|
5
|
+
|
|
6
|
+
export interface ExpoKotlinBundle {
|
|
7
|
+
// module.url is relative to the release's asset base
|
|
8
|
+
metadata: ExpoKotlinCompilationResult
|
|
9
|
+
assets: { path: string; bytes: Buffer; contentType: string }[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function buildExpoKotlinBundle(
|
|
13
|
+
appRoot: string,
|
|
14
|
+
): Promise<ExpoKotlinBundle | null> {
|
|
15
|
+
const metadata = await compileExpoKotlinModules(appRoot, '')
|
|
16
|
+
if (!metadata) return null
|
|
17
|
+
const { module } = metadata
|
|
18
|
+
if (!module) return { metadata, assets: [] }
|
|
19
|
+
const { kotlinArtifactFiles } = await import('./kotlin-host')
|
|
20
|
+
const dir = `__peach/kotlin/${module.hash}`
|
|
21
|
+
return {
|
|
22
|
+
metadata: { ...metadata, module: { ...module, url: `${dir}/app.mjs` } },
|
|
23
|
+
assets: kotlinArtifactFiles(module.hash).map((file) => ({
|
|
24
|
+
path: `${dir}/${file.name}`,
|
|
25
|
+
bytes: file.bytes,
|
|
26
|
+
contentType: file.contentType,
|
|
27
|
+
})),
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// local Expo modules on an Android device: expo's own autolinker selects the
|
|
2
|
+
// app-owned Android modules, as its gradle plugin does, and their Kotlin
|
|
3
|
+
// compiles into one Kotlin/Wasm process with the expo-modules-core DSL port.
|
|
4
|
+
import { execFile } from 'node:child_process'
|
|
5
|
+
import fs from 'node:fs'
|
|
6
|
+
import { createRequire } from 'node:module'
|
|
7
|
+
import path from 'node:path'
|
|
8
|
+
import { promisify } from 'node:util'
|
|
9
|
+
import { RNX_FORKED_SCRIPT_ENV } from '../cli/internal-child'
|
|
10
|
+
import { isLoopbackHost } from './backend-origin'
|
|
11
|
+
import { isRecord } from './is-record'
|
|
12
|
+
import type { ExpoKotlinCompilationResult } from './expoKotlinContract'
|
|
13
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
14
|
+
|
|
15
|
+
const run = promisify(execFile)
|
|
16
|
+
// the compiled CLI's execPath is peach itself, which runs a forked `.js` as a
|
|
17
|
+
// script only with this set; node and bun ignore it.
|
|
18
|
+
const scriptEnv = { ...process.env, [RNX_FORKED_SCRIPT_ENV]: '1' }
|
|
19
|
+
export const EXPO_KOTLIN_ROUTE = '/__peach/expo-kotlin'
|
|
20
|
+
const MODULE_ROOT = path.resolve(import.meta.dirname, '../kotlin')
|
|
21
|
+
|
|
22
|
+
export interface ExpoKotlinModules {
|
|
23
|
+
// the registration names Android's ExpoModulesPackageList passes, in order
|
|
24
|
+
modules: { classifier: string; name: string | null }[]
|
|
25
|
+
sources: string[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function collectExpoKotlinModules(
|
|
29
|
+
appRoot: string,
|
|
30
|
+
): Promise<ExpoKotlinModules | null> {
|
|
31
|
+
appRoot = fs.realpathSync(appRoot)
|
|
32
|
+
let autolinker: string
|
|
33
|
+
try {
|
|
34
|
+
autolinker = createRequire(path.join(appRoot, 'package.json')).resolve(
|
|
35
|
+
'expo-modules-autolinking/package.json',
|
|
36
|
+
)
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (isRecord(error) && error.code === 'MODULE_NOT_FOUND') return null
|
|
39
|
+
throw error
|
|
40
|
+
}
|
|
41
|
+
const { stdout } = await run(
|
|
42
|
+
process.execPath,
|
|
43
|
+
[
|
|
44
|
+
path.join(path.dirname(autolinker), 'bin/expo-modules-autolinking.js'),
|
|
45
|
+
'resolve',
|
|
46
|
+
'--platform',
|
|
47
|
+
'android',
|
|
48
|
+
'--project-root',
|
|
49
|
+
appRoot,
|
|
50
|
+
'--json',
|
|
51
|
+
],
|
|
52
|
+
{ timeout: 30_000, maxBuffer: 8 * 1024 * 1024, env: scriptEnv },
|
|
53
|
+
)
|
|
54
|
+
const resolved: unknown = JSON.parse(stdout)
|
|
55
|
+
if (!isRecord(resolved) || !Array.isArray(resolved.modules))
|
|
56
|
+
throw new Error('invalid Expo Android resolution')
|
|
57
|
+
const result: ExpoKotlinModules = { modules: [], sources: [] }
|
|
58
|
+
for (const entry of resolved.modules) {
|
|
59
|
+
if (!isRecord(entry) || !Array.isArray(entry.projects))
|
|
60
|
+
throw new Error('invalid Expo Android module entry')
|
|
61
|
+
for (const project of entry.projects) {
|
|
62
|
+
if (
|
|
63
|
+
!isRecord(project) ||
|
|
64
|
+
typeof project.sourceDir !== 'string' ||
|
|
65
|
+
!Array.isArray(project.modules)
|
|
66
|
+
)
|
|
67
|
+
throw new Error('invalid Expo Android project')
|
|
68
|
+
const sourceDir = fs.realpathSync(project.sourceDir)
|
|
69
|
+
// installed dependency implementations remain with their native seams.
|
|
70
|
+
if (sourceDir.split(path.sep).includes('node_modules')) continue
|
|
71
|
+
if (project.modules.length === 0) continue
|
|
72
|
+
for (const module of project.modules) {
|
|
73
|
+
if (
|
|
74
|
+
!isRecord(module) ||
|
|
75
|
+
typeof module.classifier !== 'string' ||
|
|
76
|
+
!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)+$/.test(
|
|
77
|
+
module.classifier,
|
|
78
|
+
) ||
|
|
79
|
+
(module.name !== null && typeof module.name !== 'string')
|
|
80
|
+
)
|
|
81
|
+
throw new Error(`${sourceDir}: invalid Expo Android module descriptor`)
|
|
82
|
+
result.modules.push({ classifier: module.classifier, name: module.name })
|
|
83
|
+
}
|
|
84
|
+
const walk = (dir: string) => {
|
|
85
|
+
if (!fs.existsSync(dir)) return
|
|
86
|
+
for (const child of fs
|
|
87
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
88
|
+
.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
89
|
+
const file = path.join(dir, child.name)
|
|
90
|
+
if (child.isSymbolicLink())
|
|
91
|
+
throw new Error(`Expo Kotlin source is a symlink: ${file}`)
|
|
92
|
+
if (child.isDirectory()) walk(file)
|
|
93
|
+
else if (child.name.endsWith('.java'))
|
|
94
|
+
throw new Error(`${file}: Java sources in Expo modules are not supported yet`)
|
|
95
|
+
else if (child.name.endsWith('.kt')) result.sources.push(file)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// the gradle android plugin's default main source sets
|
|
99
|
+
walk(path.join(sourceDir, 'src', 'main', 'java'))
|
|
100
|
+
walk(path.join(sourceDir, 'src', 'main', 'kotlin'))
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return result.modules.length === 0 ? null : result
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function compileExpoKotlinModules(
|
|
107
|
+
root: string,
|
|
108
|
+
origin: string,
|
|
109
|
+
): Promise<ExpoKotlinCompilationResult | null> {
|
|
110
|
+
const found = await collectExpoKotlinModules(root)
|
|
111
|
+
if (!found) return null
|
|
112
|
+
const namespaces = found.modules.map(
|
|
113
|
+
(module) => module.name ?? module.classifier.split('.').pop()!,
|
|
114
|
+
)
|
|
115
|
+
const { expoKotlinArtifact } = await import('./kotlin-host')
|
|
116
|
+
try {
|
|
117
|
+
const artifact = await expoKotlinArtifact(root, MODULE_ROOT, origin, found)
|
|
118
|
+
return { namespaces, module: artifact, failures: [] }
|
|
119
|
+
} catch (error) {
|
|
120
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
121
|
+
// kotlinc names each failing source line; anything else is the host's
|
|
122
|
+
const errors = message.match(/^.+\.kt:\d+:\d+: error:.*$/gm)
|
|
123
|
+
if (!errors) throw error
|
|
124
|
+
return {
|
|
125
|
+
namespaces,
|
|
126
|
+
module: null,
|
|
127
|
+
failures: namespaces.map((namespace) => ({
|
|
128
|
+
namespace,
|
|
129
|
+
diagnostic: errors.join('\n'),
|
|
130
|
+
})),
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const compiling = new Map<string, Promise<ExpoKotlinCompilationResult | null>>()
|
|
136
|
+
|
|
137
|
+
export async function handleExpoKotlinRequest(
|
|
138
|
+
req: IncomingMessage,
|
|
139
|
+
res: ServerResponse,
|
|
140
|
+
): Promise<boolean> {
|
|
141
|
+
const url = new URL(req.url ?? '/', 'http://localhost')
|
|
142
|
+
if (
|
|
143
|
+
!url.pathname.startsWith('/__peach/kotlin-artifact/') &&
|
|
144
|
+
url.pathname !== EXPO_KOTLIN_ROUTE
|
|
145
|
+
)
|
|
146
|
+
return false
|
|
147
|
+
const remote = req.socket.remoteAddress?.replace(/^::ffff:/, '')
|
|
148
|
+
const requestOrigin = req.headers.origin
|
|
149
|
+
if (
|
|
150
|
+
!remote ||
|
|
151
|
+
!isLoopbackHost(remote) ||
|
|
152
|
+
(requestOrigin && !isLoopbackHost(new URL(requestOrigin).hostname))
|
|
153
|
+
) {
|
|
154
|
+
res.writeHead(403)
|
|
155
|
+
res.end('Expo Kotlin artifacts require a loopback client')
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
res.setHeader('Access-Control-Allow-Origin', requestOrigin ?? '*')
|
|
159
|
+
res.setHeader('Vary', 'Origin')
|
|
160
|
+
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin')
|
|
161
|
+
if (url.pathname !== EXPO_KOTLIN_ROUTE) {
|
|
162
|
+
const { serveKotlinArtifact } = await import('./kotlin-host')
|
|
163
|
+
return serveKotlinArtifact(url.pathname, res)
|
|
164
|
+
}
|
|
165
|
+
if (req.method !== 'GET') {
|
|
166
|
+
res.writeHead(405)
|
|
167
|
+
res.end('GET required')
|
|
168
|
+
return true
|
|
169
|
+
}
|
|
170
|
+
const root = url.searchParams.get('root')
|
|
171
|
+
if (!root || !path.isAbsolute(root)) {
|
|
172
|
+
res.writeHead(400)
|
|
173
|
+
res.end('missing absolute app root')
|
|
174
|
+
return true
|
|
175
|
+
}
|
|
176
|
+
const host = req.headers.host
|
|
177
|
+
if (!host) {
|
|
178
|
+
res.writeHead(400)
|
|
179
|
+
res.end('Expo Kotlin request has no host')
|
|
180
|
+
return true
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
let pending = compiling.get(root)
|
|
184
|
+
if (!pending) {
|
|
185
|
+
pending = compileExpoKotlinModules(root, `http://${host}`)
|
|
186
|
+
compiling.set(root, pending)
|
|
187
|
+
}
|
|
188
|
+
const metadata = await pending
|
|
189
|
+
res.writeHead(200, {
|
|
190
|
+
'Content-Type': 'application/json',
|
|
191
|
+
'Cache-Control': 'no-store',
|
|
192
|
+
})
|
|
193
|
+
res.end(JSON.stringify(metadata))
|
|
194
|
+
} catch (error) {
|
|
195
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' })
|
|
196
|
+
res.end(error instanceof Error ? error.message : String(error))
|
|
197
|
+
} finally {
|
|
198
|
+
compiling.delete(root)
|
|
199
|
+
}
|
|
200
|
+
return true
|
|
201
|
+
}
|
package/src/expo-swift-host.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// expo's own autolinker selects app-owned Swift modules; the common Swift host
|
|
2
|
-
//
|
|
2
|
+
// links their registrations together and isolates namespaces only when that fails.
|
|
3
3
|
import { execFile } from 'node:child_process'
|
|
4
4
|
import { createHash } from 'node:crypto'
|
|
5
5
|
import fs from 'node:fs'
|
|
@@ -232,7 +232,7 @@ export async function collectExpoSwiftSources(
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
function expoEntry(units: ExpoSwiftSourceUnit[]): string {
|
|
235
|
-
return `import SwiftUI\nimport ExpoModulesCore\n${[...new Set(units.map((unit) => unit.namespace))].map((namespace) => `import ${namespace}`).join('\n')}\n\n@main struct PeachExpoApp:
|
|
235
|
+
return `import SwiftUI\nimport ExpoModulesCore\n${[...new Set(units.map((unit) => unit.namespace))].map((namespace) => `import ${namespace}`).join('\n')}\n\n@main struct PeachExpoApp: PeachPackage {\n init() { MainActor.assumeIsolated { PeachExpoRuntime.register([${units.flatMap((unit) => unit.classes).join(', ')}]) } }\n func view(props: JSON) -> PeachExpoNativeView { PeachExpoNativeView(props: props) }\n}\n\n@_expose(wasm, "peach_expo_call")\n@_cdecl("peach_expo_call")\n@MainActor public func peachExpoCall(_ payload: UnsafePointer<UInt8>?, _ length: Int32) -> UnsafeMutablePointer<UInt8>? {\n PeachExpoRuntime.dispatch(payload, length)\n}\n`
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
const compiling = new Map<string, Promise<unknown>>()
|
|
@@ -246,7 +246,8 @@ export async function compileExpoSwiftModules(
|
|
|
246
246
|
): Promise<ExpoSwiftCompilationResult | null> {
|
|
247
247
|
const units = await collectExpoSwiftSources(root)
|
|
248
248
|
if (!units) return null
|
|
249
|
-
//
|
|
249
|
+
// every candidate links into one app first; only a failed link compiles each
|
|
250
|
+
// namespace alone to isolate the failures, so a framework change costs one build.
|
|
250
251
|
const module = await resolveSwiftModule()
|
|
251
252
|
if (!module && publishArtifact)
|
|
252
253
|
throw new Error('No local Swift toolchain or distribution is available')
|
|
@@ -255,81 +256,42 @@ export async function compileExpoSwiftModules(
|
|
|
255
256
|
modules: [],
|
|
256
257
|
failures: [],
|
|
257
258
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
for (const unit of units) {
|
|
261
|
-
if (!module) {
|
|
259
|
+
if (!module) {
|
|
260
|
+
for (const unit of units)
|
|
262
261
|
result.failures.push({
|
|
263
262
|
namespace: unit.moduleId,
|
|
264
263
|
diagnostic: 'No local Swift toolchain or distribution is available',
|
|
265
264
|
})
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
return result
|
|
266
|
+
}
|
|
267
|
+
const key = createHash('sha256').update(fs.realpathSync(root)).digest('hex')
|
|
268
|
+
const failures = new Map<string, string>()
|
|
269
|
+
const candidates: ExpoSwiftSourceUnit[] = []
|
|
270
|
+
const stamps = new Map<ExpoSwiftSourceUnit, string>()
|
|
271
|
+
for (const unit of units) {
|
|
268
272
|
const packagePath = path.join(os.tmpdir(), 'peach-expo-swift', key, unit.moduleId)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
result.failures.push({
|
|
286
|
-
namespace: unit.moduleId,
|
|
287
|
-
diagnostic: failed.diagnostic,
|
|
288
|
-
})
|
|
289
|
-
continue
|
|
290
|
-
}
|
|
291
|
-
failedCompilations.delete(packagePath)
|
|
292
|
-
await artifactFor(projectFor(packagePath), EXPO_SWIFT_ENTRY, module.root)
|
|
293
|
-
linked.push(unit)
|
|
294
|
-
} catch (error) {
|
|
295
|
-
const diagnostic = error instanceof Error ? error.message : String(error)
|
|
296
|
-
const appPaths = Object.values(unit.files).flatMap((source) => {
|
|
297
|
-
const match = /^#sourceLocation\(file: (".*"), line: 1\)$/m.exec(source)
|
|
298
|
-
if (!match) return []
|
|
299
|
-
const file: unknown = JSON.parse(match[1]!)
|
|
300
|
-
return typeof file === 'string' ? [file] : []
|
|
301
|
-
})
|
|
302
|
-
const errors = diagnostic.match(/^.+:\d+(?::\d+)?: error:.*$/gm) ?? []
|
|
303
|
-
const sourceErrors = errors.filter(
|
|
304
|
-
(line) =>
|
|
305
|
-
appPaths.some((file) => line.startsWith(`${file}:`)) &&
|
|
306
|
-
!/external macro implementation type|failed to load library plugin/.test(line),
|
|
307
|
-
)
|
|
308
|
-
if (
|
|
309
|
-
publishArtifact &&
|
|
310
|
-
(sourceErrors.length === 0 || sourceErrors.length !== errors.length) &&
|
|
311
|
-
!(error instanceof SwiftMissingModulesError)
|
|
312
|
-
)
|
|
313
|
-
throw error
|
|
314
|
-
if (
|
|
315
|
-
sourceErrors.length &&
|
|
316
|
-
sourceErrors.length === errors.length &&
|
|
317
|
-
module &&
|
|
318
|
-
stamp &&
|
|
319
|
-
sourceStamp(packagePath, module.root) === stamp
|
|
320
|
-
) {
|
|
321
|
-
failedCompilations.set(packagePath, {
|
|
322
|
-
stamp,
|
|
323
|
-
diagnostic: sourceErrors.join('\n'),
|
|
324
|
-
})
|
|
325
|
-
}
|
|
326
|
-
result.failures.push({
|
|
327
|
-
namespace: unit.moduleId,
|
|
328
|
-
diagnostic: sourceErrors.length ? sourceErrors.join('\n') : diagnostic,
|
|
329
|
-
})
|
|
273
|
+
// preserve the compiler's input stamp across unchanged reloads.
|
|
274
|
+
// only this adapter's build copy is synchronized; app sources are read only.
|
|
275
|
+
syncSwiftTree(
|
|
276
|
+
packagePath,
|
|
277
|
+
new Map(
|
|
278
|
+
Object.entries(unit.files).map(([name, source]) => [
|
|
279
|
+
path.join(packagePath, name),
|
|
280
|
+
source,
|
|
281
|
+
]),
|
|
282
|
+
),
|
|
283
|
+
)
|
|
284
|
+
const stamp = sourceStamp(packagePath, module.root)
|
|
285
|
+
const failed = failedCompilations.get(packagePath)
|
|
286
|
+
if (failed?.stamp === stamp) {
|
|
287
|
+
failures.set(unit.moduleId, failed.diagnostic)
|
|
288
|
+
continue
|
|
330
289
|
}
|
|
290
|
+
failedCompilations.delete(packagePath)
|
|
291
|
+
stamps.set(unit, stamp)
|
|
292
|
+
candidates.push(unit)
|
|
331
293
|
}
|
|
332
|
-
|
|
294
|
+
const link = async (linked: ExpoSwiftSourceUnit[]) => {
|
|
333
295
|
const files: Record<string, string> = Object.assign(
|
|
334
296
|
{},
|
|
335
297
|
...linked.map((unit) => unit.files),
|
|
@@ -355,11 +317,69 @@ export async function compileExpoSwiftModules(
|
|
|
355
317
|
]),
|
|
356
318
|
),
|
|
357
319
|
)
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
320
|
+
return artifactFor(projectFor(packagePath), EXPO_SWIFT_ENTRY, module.root)
|
|
321
|
+
}
|
|
322
|
+
let linked = candidates
|
|
323
|
+
let artifact: import('./swift-host').SwiftArtifact | undefined
|
|
324
|
+
if (linked.length) {
|
|
325
|
+
try {
|
|
326
|
+
artifact = await link(linked)
|
|
327
|
+
} catch (linkError) {
|
|
328
|
+
linked = []
|
|
329
|
+
for (const unit of candidates) {
|
|
330
|
+
const packagePath = path.join(os.tmpdir(), 'peach-expo-swift', key, unit.moduleId)
|
|
331
|
+
try {
|
|
332
|
+
await artifactFor(projectFor(packagePath), EXPO_SWIFT_ENTRY, module.root)
|
|
333
|
+
linked.push(unit)
|
|
334
|
+
} catch (error) {
|
|
335
|
+
const diagnostic = error instanceof Error ? error.message : String(error)
|
|
336
|
+
const appPaths = Object.values(unit.files).flatMap((source) => {
|
|
337
|
+
const match = /^#sourceLocation\(file: (".*"), line: 1\)$/m.exec(source)
|
|
338
|
+
if (!match) return []
|
|
339
|
+
const file: unknown = JSON.parse(match[1]!)
|
|
340
|
+
return typeof file === 'string' ? [file] : []
|
|
341
|
+
})
|
|
342
|
+
const errors = diagnostic.match(/^.+:\d+(?::\d+)?: error:.*$/gm) ?? []
|
|
343
|
+
const sourceErrors = errors.filter(
|
|
344
|
+
(line) =>
|
|
345
|
+
appPaths.some((file) => line.startsWith(`${file}:`)) &&
|
|
346
|
+
!/external macro implementation type|failed to load library plugin/.test(
|
|
347
|
+
line,
|
|
348
|
+
),
|
|
349
|
+
)
|
|
350
|
+
if (
|
|
351
|
+
publishArtifact &&
|
|
352
|
+
(sourceErrors.length === 0 || sourceErrors.length !== errors.length) &&
|
|
353
|
+
!(error instanceof SwiftMissingModulesError)
|
|
354
|
+
)
|
|
355
|
+
throw error
|
|
356
|
+
if (
|
|
357
|
+
sourceErrors.length &&
|
|
358
|
+
sourceErrors.length === errors.length &&
|
|
359
|
+
sourceStamp(packagePath, module.root) === stamps.get(unit)
|
|
360
|
+
) {
|
|
361
|
+
failedCompilations.set(packagePath, {
|
|
362
|
+
stamp: stamps.get(unit)!,
|
|
363
|
+
diagnostic: sourceErrors.join('\n'),
|
|
364
|
+
})
|
|
365
|
+
}
|
|
366
|
+
failures.set(
|
|
367
|
+
unit.moduleId,
|
|
368
|
+
sourceErrors.length ? sourceErrors.join('\n') : diagnostic,
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// every namespace compiles alone, so the link itself is what failed.
|
|
373
|
+
if (linked.length === candidates.length) throw linkError
|
|
374
|
+
if (linked.length) artifact = await link(linked)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
for (const unit of units) {
|
|
378
|
+
const diagnostic = failures.get(unit.moduleId)
|
|
379
|
+
if (diagnostic !== undefined)
|
|
380
|
+
result.failures.push({ namespace: unit.moduleId, diagnostic })
|
|
381
|
+
}
|
|
382
|
+
if (artifact) {
|
|
363
383
|
await publishArtifact?.(artifact)
|
|
364
384
|
result.modules.push({
|
|
365
385
|
namespaces: linked.map((unit) => unit.moduleId),
|