peachsim 0.1.1481 → 0.1.1619

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.
Files changed (292) hide show
  1. package/README.md +56 -13
  2. package/cli/app-catalog.ts +7 -5
  3. package/cli/app-config.ts +47 -8
  4. package/cli/app-fonts.ts +84 -49
  5. package/cli/app-stubs.ts +433 -0
  6. package/cli/bin.ts +15 -2
  7. package/cli/bridge-flow-runner.ts +383 -123
  8. package/cli/cloud-client.ts +26 -25
  9. package/cli/cloud-test-tunnel.ts +2 -0
  10. package/cli/command-registry.ts +2 -1
  11. package/cli/commands/agent-indicator-mode.ts +39 -0
  12. package/cli/commands/box/bundle-plane.ts +4 -0
  13. package/cli/commands/box.ts +16 -23
  14. package/cli/commands/control.ts +221 -126
  15. package/cli/commands/daemon.ts +99 -24
  16. package/cli/commands/debug.ts +31 -3
  17. package/cli/commands/detect.ts +1 -1
  18. package/cli/commands/device.ts +6 -8
  19. package/cli/commands/diagnose.ts +2 -0
  20. package/cli/commands/do-chain.ts +2 -2
  21. package/cli/commands/doctor.ts +1030 -0
  22. package/cli/commands/flow.ts +447 -406
  23. package/cli/commands/inspect/actions.ts +2 -0
  24. package/cli/commands/inspect/core.ts +138 -46
  25. package/cli/commands/inspect/env.ts +10 -0
  26. package/cli/commands/inspect/list.ts +25 -8
  27. package/cli/commands/inspect/memory.ts +9 -0
  28. package/cli/commands/inspect/settling.ts +219 -66
  29. package/cli/commands/inspect/shared.ts +53 -12
  30. package/cli/commands/inspect/wait-event.ts +0 -1
  31. package/cli/commands/inspect/wait.ts +2 -2
  32. package/cli/commands/inspect.ts +51 -16
  33. package/cli/commands/install-desktop.ts +3 -2
  34. package/cli/commands/kotlin.ts +81 -22
  35. package/cli/commands/login.ts +1 -1
  36. package/cli/commands/maestro-compare.ts +41 -5
  37. package/cli/commands/maestro-generate.ts +11 -7
  38. package/cli/commands/maestro.ts +43 -15
  39. package/cli/commands/mode.ts +13 -1
  40. package/cli/commands/no-bridge-hint.ts +26 -3
  41. package/cli/commands/perf-record.ts +296 -0
  42. package/cli/commands/perf.ts +6 -1
  43. package/cli/commands/platform.ts +15 -4
  44. package/cli/commands/preview.ts +1 -1
  45. package/cli/commands/record.ts +37 -6
  46. package/cli/commands/report-issue.ts +2 -1
  47. package/cli/commands/runtime.ts +9 -11
  48. package/cli/commands/screenshots.ts +4 -3
  49. package/cli/commands/serve.ts +30 -2
  50. package/cli/commands/setup.ts +32 -7
  51. package/cli/commands/skills.ts +16 -43
  52. package/cli/commands/swift.ts +22 -3
  53. package/cli/commands/upgrade.ts +7 -6
  54. package/cli/commands/upload.ts +118 -69
  55. package/cli/commands/window.ts +449 -0
  56. package/cli/current-sim.ts +1 -2
  57. package/cli/desktop-companion.ts +84 -10
  58. package/cli/device-option.ts +8 -0
  59. package/cli/drivers/desktop.ts +3 -1
  60. package/cli/drivers/playwright-sim-host.ts +30 -11
  61. package/cli/drivers/playwright.ts +1 -4
  62. package/cli/flow-export.ts +2 -2
  63. package/cli/flow-file.ts +19 -18
  64. package/cli/internal-child.ts +7 -6
  65. package/cli/maestro-compare.ts +28 -6
  66. package/cli/maestro-js.ts +2 -1
  67. package/cli/main.ts +25 -4
  68. package/cli/material-symbol-fonts.ts +171 -0
  69. package/cli/outbound-endpoints.ts +132 -54
  70. package/cli/parse-args.ts +7 -0
  71. package/cli/preview-capture.ts +12 -0
  72. package/cli/privacy.ts +1 -1
  73. package/cli/run-registry.ts +5 -2
  74. package/cli/runtime-compat.ts +16 -0
  75. package/cli/telemetry.ts +6 -3
  76. package/cli/ui.ts +5 -0
  77. package/cli/ws-bridge.ts +121 -62
  78. package/detox/attempt-events.cjs +762 -0
  79. package/detox/attempt-events.d.cts +106 -0
  80. package/detox/gestures.ts +115 -2
  81. package/detox/index.ts +762 -426
  82. package/detox/jest-environment.cjs +86 -0
  83. package/detox/jest-preset.cjs +3 -1
  84. package/detox/jest-setup-after-env.cjs +1 -0
  85. package/detox/matchers.ts +3 -6
  86. package/detox/motion-change.cjs +259 -0
  87. package/detox/motion-change.d.cts +73 -0
  88. package/detox/proof-frame.cjs +83 -9
  89. package/dist-lib/agent-daemon-client.cjs +1 -1
  90. package/dist-lib/agent-events.cjs +1 -1
  91. package/dist-lib/attached-projects.cjs +1 -1
  92. package/dist-lib/auth/shared-session.cjs +15 -5
  93. package/dist-lib/backend-origin.cjs +1 -1
  94. package/dist-lib/beta.cjs +1 -1
  95. package/dist-lib/beta.mjs +1 -1
  96. package/dist-lib/bridge-constants.cjs +3 -1
  97. package/dist-lib/bridge-contract-input.cjs +1 -1
  98. package/dist-lib/bridge-contract-input.mjs +1 -1
  99. package/dist-lib/bridge-contract.cjs +1 -1
  100. package/dist-lib/bridge-contract.mjs +1 -1
  101. package/dist-lib/bundle-channel.cjs +5950 -1775
  102. package/dist-lib/capture-contract.cjs +1 -1
  103. package/dist-lib/capture-contract.mjs +1 -1
  104. package/dist-lib/cloud-contract.cjs +6 -1
  105. package/dist-lib/cloud-contract.mjs +5 -1
  106. package/dist-lib/cloud-test-args.cjs +1 -1
  107. package/dist-lib/cloud-test-args.mjs +1 -1
  108. package/dist-lib/cloud.cjs +1 -1
  109. package/dist-lib/cloud.mjs +1 -1
  110. package/dist-lib/config.cjs +34 -3
  111. package/dist-lib/config.mjs +33 -3
  112. package/dist-lib/detox/index.cjs +3443 -564
  113. package/dist-lib/detox/jest-environment.cjs +86 -0
  114. package/dist-lib/detox/jest-preset.cjs +3 -1
  115. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  116. package/dist-lib/expo-swift-bundle.cjs +5518 -1446
  117. package/dist-lib/expo-swift-metadata.cjs +21 -13
  118. package/dist-lib/expo-swift-metadata.mjs +21 -13
  119. package/dist-lib/home-paths.cjs +1 -1
  120. package/dist-lib/host/bridge-host.cjs +21728 -16808
  121. package/dist-lib/host/fetch-proxy-handler.cjs +177 -102
  122. package/dist-lib/host/fetch-proxy-overrides.cjs +84 -4
  123. package/dist-lib/host/fetch-proxy-overrides.mjs +74 -3
  124. package/dist-lib/host/replacement-module-handler.cjs +16 -15
  125. package/dist-lib/host/websocket-proxy.cjs +243 -6
  126. package/dist-lib/index.cjs +6063 -4113
  127. package/dist-lib/jump-to-source-babel.cjs +73 -35
  128. package/dist-lib/jump-to-source-native.cjs +118 -10
  129. package/dist-lib/kotlin-host.cjs +264 -121
  130. package/dist-lib/menu.cjs +1 -1
  131. package/dist-lib/menu.mjs +1 -1
  132. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  133. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  134. package/dist-lib/metro-production-bundle.cjs +147 -99
  135. package/dist-lib/metro-production-bundle.mjs +142 -99
  136. package/dist-lib/metro.cjs +4249 -360
  137. package/dist-lib/native-app-identity-project.cjs +318 -131
  138. package/dist-lib/native-app-identity-project.mjs +317 -131
  139. package/dist-lib/native-app-identity.cjs +322 -33
  140. package/dist-lib/native-app-identity.mjs +319 -32
  141. package/dist-lib/native-firebase-metadata.cjs +1 -1
  142. package/dist-lib/native-firebase-metadata.mjs +1 -1
  143. package/dist-lib/plist.cjs +1 -1
  144. package/dist-lib/plist.mjs +1 -1
  145. package/dist-lib/profiles.cjs +1 -1
  146. package/dist-lib/public-brand.cjs +1 -1
  147. package/dist-lib/react-native-host-modules.cjs +11 -1
  148. package/dist-lib/react-native-host-modules.mjs +11 -1
  149. package/dist-lib/scripts/dev-server-scanner.cjs +127 -42
  150. package/dist-lib/sdk.cjs +311 -103
  151. package/dist-lib/sdk.mjs +311 -103
  152. package/dist-lib/skills.cjs +18344 -11121
  153. package/dist-lib/swift-compile.cjs +3291 -544
  154. package/dist-lib/swift-compile.mjs +3227 -478
  155. package/dist-lib/swift-package.cjs +25 -3
  156. package/dist-lib/swift-package.mjs +22 -2
  157. package/dist-lib/swift-submit.cjs +2 -2
  158. package/dist-lib/swift-submit.mjs +2 -2
  159. package/dist-lib/swift-toolchain.cjs +1 -1
  160. package/dist-lib/swift.cjs +1 -1
  161. package/dist-lib/vite-swift.cjs +5523 -1428
  162. package/dist-lib/vite.cjs +251 -60
  163. package/dist-lib/worklet-executable.cjs +18 -4
  164. package/dist-lib/worklet-executable.mjs +18 -4
  165. package/install.sh +8 -3
  166. package/kotlin/AndroidViewSources/android/graphics/Canvas.kt +2 -0
  167. package/kotlin/AndroidViewSources/android/graphics/Paint.kt +18 -0
  168. package/kotlin/AndroidViewSources/dev/peach/app/JavaFormat.kt +16 -8
  169. package/kotlin/AppSources/android/view/Window.kt +4 -1
  170. package/kotlin/AppSources/android/window/BackEvent.kt +24 -0
  171. package/kotlin/AppSources/android/window/OnBackAnimationCallback.kt +7 -0
  172. package/kotlin/AppSources/android/window/OnBackInvokedDispatcher.kt +37 -1
  173. package/kotlin/AppSources/androidx/activity/BackEventCompat.kt +5 -0
  174. package/kotlin/AppSources/androidx/activity/OnBackPressedDispatcher.kt +8 -1
  175. package/kotlin/AppSources/dev/peach/app/PeachApp.kt +14 -0
  176. package/kotlin/CompilerSources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar +1 -0
  177. package/kotlin/CompilerSources/PeachJniIncoming.kt +243 -0
  178. package/kotlin/CompilerSources/PeachJniPlugin.kt +116 -0
  179. package/kotlin/ComposeSources/PeachComposeHost.kt +58 -32
  180. package/kotlin/ComposeSources/PeachMagnifier.kt +185 -0
  181. package/kotlin/ExpoComposeSources/PeachAndroidView.kt +16 -0
  182. package/kotlin/JniPlatformSources/androidx/annotation/CallSuper.kt +5 -0
  183. package/kotlin/JniPlatformSources/com/facebook/proguard/annotations/DoNotStrip.kt +5 -0
  184. package/kotlin/JniPlatformSources/dalvik/annotation/optimization/FastNative.kt +5 -0
  185. package/kotlin/JniPlatformSources/kotlin/Synchronized.kt +6 -0
  186. package/kotlin/JniPlatformSources/kotlin/jvm/JvmField.kt +5 -0
  187. package/kotlin/JniPlatformSources/kotlin/jvm/JvmStatic.kt +5 -0
  188. package/kotlin/JniSources/com/facebook/jni/HybridData.kt +68 -0
  189. package/kotlin/JniSources/dev/peach/jni/IncomingBindings.kt +157 -0
  190. package/kotlin/JniSources/dev/peach/jni/NativeBindings.kt +79 -0
  191. package/kotlin/JniTransportSources/dev/peach/jni/integration/JniTransport.kt +91 -0
  192. package/kotlin/README.md +18 -0
  193. package/kotlin/Sources/java/util/IllegalFormatCodePointException.kt +10 -0
  194. package/kotlin/Sources/java/util/IllegalFormatException.kt +5 -0
  195. package/kotlin/fixtures/counter/CounterApp.native.tsx +30 -3
  196. package/kotlin/fixtures/counter/CounterScreen.d.kt.ts +1 -0
  197. package/kotlin/fixtures/counter/CounterScreen.kt +2 -1
  198. package/kotlin/fixtures/counter/package.json +15 -2
  199. package/kotlin/fixtures/counter/vite.config.ts +11 -1
  200. package/kotlin/fork/NOTICE +1 -0
  201. package/kotlin/fork/README.md +5 -4
  202. package/kotlin/fork/compose-worker-globals.patch +629 -0
  203. package/kotlin/fork/fork.json +1 -1
  204. package/kotlin/toolchain.json +3 -3
  205. package/package.json +1 -1
  206. package/scripts/dev-server-scanner.ts +166 -81
  207. package/skills/contrast/SKILL.md +4 -4
  208. package/skills/peach-debug/SKILL.md +1 -1
  209. package/skills/peach-perf/SKILL.md +25 -0
  210. package/skills/peach-setup/SKILL.md +47 -13
  211. package/skills/peach-test/SKILL.md +2 -3
  212. package/src/agent-identity.ts +18 -0
  213. package/src/assetCatalogContract.ts +31 -3
  214. package/src/auth/shared-session.ts +3 -2
  215. package/src/bridge-constants.ts +1 -1
  216. package/src/bridge-contract.ts +5 -0
  217. package/src/bundle-channel.mjs +38 -7
  218. package/src/bundleGlobalFooter.ts +70 -20
  219. package/src/cloud-contract.ts +11 -0
  220. package/src/config.ts +47 -1
  221. package/src/configured-bundle-producer.ts +4 -1
  222. package/src/desktop-host-delivery.ts +4 -3
  223. package/src/expo-kotlin-host.ts +7 -2
  224. package/src/guest-bundle.ts +15 -8
  225. package/src/host/agent-host.ts +2 -4
  226. package/src/host/bridge-host.ts +260 -119
  227. package/src/host/fetch-proxy-handler.ts +237 -146
  228. package/src/host/fetch-proxy-overrides.ts +118 -0
  229. package/src/host/fetch-proxy-transport.ts +77 -63
  230. package/src/host/lease-owner.test.ts +18 -1
  231. package/src/host/lease-owner.ts +4 -5
  232. package/src/host/loopback-ports.ts +34 -0
  233. package/src/host/replacement-module-handler.ts +24 -15
  234. package/src/host/server-scan-handler.ts +87 -0
  235. package/src/host/staged-host-files.ts +92 -0
  236. package/src/host/websocket-proxy.ts +271 -5
  237. package/src/installed-package-versions.ts +66 -0
  238. package/src/jump-to-source-babel.ts +97 -89
  239. package/src/jump-to-source-native.ts +109 -11
  240. package/src/kotlin-host.ts +144 -27
  241. package/src/kotlin-module.ts +52 -0
  242. package/src/kotlinDiscoveryContract.ts +27 -0
  243. package/src/metro-layout-parser.ts +78 -0
  244. package/src/metro-plugin.ts +129 -39
  245. package/src/metro-production-bundle.ts +140 -139
  246. package/src/native-app-identity-project.ts +103 -5
  247. package/src/native-app-identity.ts +173 -11
  248. package/src/native-firebase-config.ts +83 -80
  249. package/src/native-seam-manifest.ts +0 -44
  250. package/src/nativeAppIconContract.ts +137 -0
  251. package/src/nativeViewSourceContract.ts +49 -0
  252. package/src/nativeViewWebContract.ts +41 -0
  253. package/src/nitro-bundle.ts +43 -0
  254. package/src/nitro-compile.ts +107 -0
  255. package/src/nitro-source.ts +310 -0
  256. package/src/nitroContract.ts +51 -0
  257. package/src/one-widget-metadata.ts +23 -3
  258. package/src/oneWidgetContract.ts +36 -3
  259. package/src/packageVersionsContract.ts +52 -0
  260. package/src/process-group.ts +306 -0
  261. package/src/process-liveness.ts +24 -0
  262. package/src/process-output.ts +21 -2
  263. package/src/react-devtools-frontend-react.ts +45 -0
  264. package/src/react-native-host-modules.ts +10 -0
  265. package/src/serverDiscoveryContract.ts +41 -0
  266. package/src/skills/builtin/maestro-test.ts +2 -4
  267. package/src/swift-compile.ts +3195 -650
  268. package/src/swift-design-time.ts +552 -0
  269. package/src/swift-distribution-contract.ts +11 -0
  270. package/src/swift-distribution.lock.json +46 -11
  271. package/src/swift-host.ts +144 -30
  272. package/src/swift-icu.ts +10 -2
  273. package/src/swift-objc-sdk.ts +726 -0
  274. package/src/swift-objc-standin.ts +441 -0
  275. package/src/swift-package.ts +25 -0
  276. package/src/swift-packages.ts +25 -10
  277. package/src/swift-standin-config.ts +94 -0
  278. package/src/swift-standin-emit.ts +5 -3
  279. package/src/swift-standins.ts +225 -81
  280. package/src/swift-submit.ts +2 -1
  281. package/src/swiftContract.ts +4 -0
  282. package/src/target-matcher.ts +67 -0
  283. package/src/visit-parsed-program.ts +21 -0
  284. package/src/vite-plugin-one.ts +65 -17
  285. package/src/vite-plugin-swift.ts +1 -0
  286. package/src/vite-plugin.ts +168 -70
  287. package/src/worker-system-chunks.ts +89 -10
  288. package/src/worklet-executable.ts +21 -3
  289. package/src/worklets-babel.ts +85 -13
  290. package/src/xcode-project.ts +81 -134
  291. package/src/xcode-target-members.ts +107 -0
  292. package/cli/commands/compat.ts +0 -395
@@ -0,0 +1,433 @@
1
+ // native stubs keyed by the package they answer (specs/peach-app-stubs.md):
2
+ // the app's peach.config.ts `packages`, installed `peach-stub-<package>`
3
+ // packages, and a library's own `peach` field, lowered into the engine's
4
+ // nativeModules / nativeViews wire slots.
5
+
6
+ import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs'
7
+ import { dirname, join, relative, resolve } from 'node:path'
8
+ import { resolveCompatVersionRange } from '@peach/compat/version-range'
9
+ import { parseSync, type Expression } from 'oxc-parser'
10
+ import { transform } from 'sucrase'
11
+ import {
12
+ isPackageStubs,
13
+ type NativeModuleResolution,
14
+ type NativeStub,
15
+ type NativeViewResolution,
16
+ type NativeViewSource,
17
+ type PackageStubs,
18
+ type PeachConfig,
19
+ } from '../src/config'
20
+ import { isRecord } from '../src/is-record'
21
+ import { visitParsedProgram } from '../src/visit-parsed-program'
22
+ import { readProjectManifest, scanRepository } from './commands/detect'
23
+
24
+ // who answers a package, in rising precedence
25
+ export type StubOrigin = 'library' | 'stub-package' | 'config'
26
+
27
+ export interface AppliedStubs {
28
+ target: string
29
+ origin: StubOrigin
30
+ // the package declaring the stubs; null for peach.config.ts
31
+ provider: string | null
32
+ providerVersion: string | null
33
+ modules: string[]
34
+ views: string[]
35
+ viewSources?: Record<string, NativeViewSource>
36
+ }
37
+
38
+ // a published stub whose `versions` range excludes the installed target
39
+ export interface SkippedStubs {
40
+ target: string
41
+ provider: string
42
+ providerVersion: string | null
43
+ versions: string
44
+ installed: string
45
+ }
46
+
47
+ export interface AppStubs {
48
+ applied: AppliedStubs[]
49
+ skipped: SkippedStubs[]
50
+ }
51
+
52
+ interface StubSource {
53
+ target: string
54
+ origin: StubOrigin
55
+ provider: string | null
56
+ providerVersion: string | null
57
+ stubs: PackageStubs
58
+ // where relative stub paths resolve from
59
+ baseDir: string
60
+ }
61
+
62
+ // `peach-stub-<package>` or `@scope/peach-stub-<package>`; a scoped target uses
63
+ // the DefinitelyTyped encoding, `@acme/scanner` as `peach-stub-acme__scanner`.
64
+ export function stubPackageTarget(name: string): string | null {
65
+ const match = /^(?:@[^/]+\/)?peach-stub-(.+)$/.exec(name)
66
+ if (!match?.[1]) return null
67
+ const split = match[1].indexOf('__')
68
+ return split > 0
69
+ ? `@${match[1].slice(0, split)}/${match[1].slice(split + 2)}`
70
+ : match[1]
71
+ }
72
+
73
+ export function resolveAppStubs(
74
+ appDir: string,
75
+ config: PeachConfig | undefined,
76
+ configDir: string,
77
+ ): { config: PeachConfig | undefined; stubs: AppStubs } {
78
+ const sources: StubSource[] = []
79
+ const skipped: SkippedStubs[] = []
80
+ const manifest = readProjectManifest(appDir)
81
+ for (const name of Object.keys({
82
+ ...manifest?.devDependencies,
83
+ ...manifest?.dependencies,
84
+ })) {
85
+ const dir = installedPackageDir(appDir, name)
86
+ const installed = dir ? readPackageJson(dir) : null
87
+ if (!dir || !installed || installed.peach === undefined) continue
88
+ const stubTarget = stubPackageTarget(name)
89
+ const target = stubTarget ?? name
90
+ const field = installed.peach
91
+ const versions = isRecord(field) ? field.versions : undefined
92
+ const stubs = isRecord(field) ? { modules: field.modules, views: field.views } : field
93
+ if (
94
+ !isPackageStubs(stubs) ||
95
+ (versions !== undefined && typeof versions !== 'string')
96
+ )
97
+ throw new Error(
98
+ `${name}'s package.json "peach" field must be "noop" or { versions?, modules?, views? } whose values are "noop" or file paths`,
99
+ )
100
+ const providerVersion =
101
+ typeof installed.version === 'string' ? installed.version : null
102
+ const targetDir = stubTarget ? installedPackageDir(appDir, target) : dir
103
+ const targetVersion = targetDir ? readPackageJson(targetDir)?.version : undefined
104
+ // a stub for a package the app does not install answers nothing
105
+ if (typeof targetVersion !== 'string') continue
106
+ if (
107
+ versions !== undefined &&
108
+ resolveCompatVersionRange([versions], targetVersion).status !== 'matched'
109
+ ) {
110
+ skipped.push({
111
+ target,
112
+ provider: name,
113
+ providerVersion,
114
+ versions,
115
+ installed: targetVersion,
116
+ })
117
+ continue
118
+ }
119
+ sources.push({
120
+ target,
121
+ origin: stubTarget ? 'stub-package' : 'library',
122
+ provider: name,
123
+ providerVersion,
124
+ stubs,
125
+ baseDir: dir,
126
+ })
127
+ }
128
+ for (const [target, stubs] of Object.entries(config?.packages ?? {})) {
129
+ sources.push({
130
+ target,
131
+ origin: 'config',
132
+ provider: null,
133
+ providerVersion: null,
134
+ stubs,
135
+ baseDir: configDir,
136
+ })
137
+ }
138
+
139
+ const ORDER: Record<StubOrigin, number> = { library: 0, 'stub-package': 1, config: 2 }
140
+ sources.sort((a, b) => ORDER[a.origin] - ORDER[b.origin])
141
+ const nativeModules: Record<string, NativeModuleResolution> = {}
142
+ const nativeViews: Record<string, NativeViewResolution> = {}
143
+ const applied: AppliedStubs[] = []
144
+ for (const source of sources) {
145
+ const { modules, views } = expandStubs(appDir, source)
146
+ for (const [name, stub] of Object.entries(modules))
147
+ nativeModules[name] = lowerStub(stub, source.baseDir)
148
+ for (const [name, stub] of Object.entries(views))
149
+ nativeViews[name] =
150
+ typeof stub === 'string' ? lowerStub(stub, source.baseDir) : stub
151
+ applied.push({
152
+ target: source.target,
153
+ origin: source.origin,
154
+ provider: source.provider,
155
+ providerVersion: source.providerVersion,
156
+ modules: Object.keys(modules),
157
+ views: Object.keys(views),
158
+ ...(Object.values(views).some((value) => typeof value !== 'string')
159
+ ? {
160
+ viewSources: Object.fromEntries(
161
+ Object.entries(views).filter(
162
+ (entry): entry is [string, NativeViewSource] =>
163
+ typeof entry[1] !== 'string',
164
+ ),
165
+ ),
166
+ }
167
+ : {}),
168
+ })
169
+ }
170
+ const stubs = { applied, skipped }
171
+ if (!config && applied.length === 0) return { config, stubs }
172
+ const lowered: PeachConfig = { ...config, nativeModules, nativeViews }
173
+ delete lowered.packages
174
+ return { config: lowered, stubs }
175
+ }
176
+
177
+ // a whole-package 'noop' answers every native name the installed package asks for
178
+ function expandStubs(
179
+ appDir: string,
180
+ source: StubSource,
181
+ ): {
182
+ modules: Record<string, NativeStub>
183
+ views: Record<string, NativeStub | NativeViewSource>
184
+ } {
185
+ if (source.stubs !== 'noop')
186
+ return { modules: source.stubs.modules ?? {}, views: source.stubs.views ?? {} }
187
+ const dir = installedPackageDir(appDir, source.target)
188
+ if (!dir)
189
+ throw new Error(
190
+ `packages['${source.target}']: 'noop' answers the names an installed package asks for, and ${source.target} is not installed`,
191
+ )
192
+ const modules: Record<string, NativeStub> = {}
193
+ const views: Record<string, NativeStub> = {}
194
+ for (const request of packageNativeRequests(dir)) {
195
+ if (request.kind === 'module') modules[request.name] = 'noop'
196
+ else views[request.name] = 'noop'
197
+ }
198
+ return { modules, views }
199
+ }
200
+
201
+ function lowerStub(stub: NativeStub, baseDir: string): 'noop' | { file: string } {
202
+ return stub === 'noop' ? stub : { file: resolve(baseDir, stub) }
203
+ }
204
+
205
+ function readPackageJson(dir: string): Record<string, unknown> | null {
206
+ const text = readSmallFile(join(dir, 'package.json'))
207
+ if (!text) return null
208
+ try {
209
+ const parsed: unknown = JSON.parse(text)
210
+ return isRecord(parsed) ? parsed : null
211
+ } catch {
212
+ return null
213
+ }
214
+ }
215
+
216
+ // the installed copy of a dependency, found the way node resolves it
217
+ export function installedPackageDir(appDir: string, name: string): string | null {
218
+ let current = appDir
219
+ while (true) {
220
+ const dir = join(current, 'node_modules', name)
221
+ if (existsSync(join(dir, 'package.json'))) return realpathSync(dir)
222
+ const parent = dirname(current)
223
+ if (parent === current) return null
224
+ current = parent
225
+ }
226
+ }
227
+
228
+ export interface NativeRequest {
229
+ kind: 'module' | 'view'
230
+ name: string
231
+ }
232
+
233
+ export const SOURCE_FILE = /\.[cm]?[jt]sx?$/
234
+ const NATIVE_LOOKUP =
235
+ /requireNative|requireOptionalNative|TurboModuleRegistry|codegenNativeComponent|NativeModules/
236
+
237
+ function nativeLookupName(node: Expression): string | null {
238
+ if (node.type === 'Identifier') return node.name
239
+ if (
240
+ node.type === 'MemberExpression' &&
241
+ !node.computed &&
242
+ node.property.type === 'Identifier'
243
+ )
244
+ return node.property.name
245
+ return null
246
+ }
247
+
248
+ // sucrase removes flow and typescript types while retaining source lines. oxc
249
+ // then visits executable syntax, including expressions inside template literals,
250
+ // without interpreting comments, fixture strings or type queries as requests.
251
+ export function sourceNativeRequests(
252
+ file: string,
253
+ text: string,
254
+ includeLegacy = false,
255
+ ): (NativeRequest & { line: number })[] {
256
+ if (!NATIVE_LOOKUP.test(text)) return []
257
+ let code: string
258
+ try {
259
+ code = transform(text, {
260
+ transforms: [/\.[cm]?tsx?$/.test(file) ? 'typescript' : 'flow', 'jsx'],
261
+ disableESTransforms: true,
262
+ }).code
263
+ } catch {
264
+ return []
265
+ }
266
+ const { program, errors } = parseSync(file, code, { lang: 'jsx' })
267
+ if (errors.length) return []
268
+ const found: (NativeRequest & { line: number })[] = []
269
+ const add = (kind: NativeRequest['kind'], name: string, at: number) => {
270
+ found.push({ kind, name, line: code.slice(0, at).split('\n').length })
271
+ }
272
+ visitParsedProgram(program, {
273
+ CallExpression(node) {
274
+ const first = node.arguments[0]
275
+ if (first?.type !== 'Literal' || typeof first.value !== 'string') return
276
+ const callee = node.callee
277
+ const lookup = nativeLookupName(callee)
278
+ if (
279
+ callee.type === 'MemberExpression' &&
280
+ !callee.computed &&
281
+ nativeLookupName(callee.object) === 'TurboModuleRegistry' &&
282
+ (lookup === 'get' || lookup === 'getEnforcing')
283
+ ) {
284
+ add('module', first.value, node.start)
285
+ } else if (lookup) {
286
+ if (['requireNativeModule', 'requireOptionalNativeModule'].includes(lookup))
287
+ add('module', first.value, node.start)
288
+ else if (['requireNativeComponent', 'codegenNativeComponent'].includes(lookup))
289
+ add('view', first.value, node.start)
290
+ else if (['requireNativeView', 'requireNativeViewManager'].includes(lookup)) {
291
+ const second = node.arguments[1]
292
+ // expo registers a named view under the module_view key.
293
+ const name =
294
+ second?.type === 'Literal' && typeof second.value === 'string'
295
+ ? `${first.value}_${second.value}`
296
+ : first.value
297
+ add('view', name, node.start)
298
+ }
299
+ }
300
+ },
301
+ MemberExpression(node) {
302
+ if (
303
+ includeLegacy &&
304
+ !node.computed &&
305
+ nativeLookupName(node.object) === 'NativeModules' &&
306
+ node.property.type === 'Identifier'
307
+ )
308
+ add('module', node.property.name, node.start)
309
+ },
310
+ })
311
+ return found
312
+ }
313
+
314
+ // the native names a package's own JavaScript asks for, outside its native
315
+ // sources (top-level ios and android; src/ios is JavaScript), tests and examples
316
+ const NATIVE_SOURCE_DIRS = new Set(['ios', 'android'])
317
+ const PACKAGE_SKIP_DIRS = new Set([
318
+ 'example',
319
+ 'examples',
320
+ '__tests__',
321
+ '__mocks__',
322
+ 'docs',
323
+ ])
324
+
325
+ export function packageNativeRequests(dir: string): NativeRequest[] {
326
+ const found = new Map<string, NativeRequest>()
327
+ const directories = scanRepository(dir).directories.filter((sub) => {
328
+ const segments = relative(dir, sub).split('/')
329
+ return (
330
+ !NATIVE_SOURCE_DIRS.has(segments[0]) &&
331
+ !segments.some((segment) => PACKAGE_SKIP_DIRS.has(segment))
332
+ )
333
+ })
334
+ let files = 0
335
+ for (const sub of directories) {
336
+ for (const name of listDir(sub)) {
337
+ if (!SOURCE_FILE.test(name) || name.endsWith('.d.ts') || files >= 2_000) continue
338
+ files++
339
+ const file = join(sub, name)
340
+ const text = readSmallFile(file)
341
+ if (!text) continue
342
+ for (const { kind, name } of sourceNativeRequests(file, text, true))
343
+ found.set(`${kind}:${name}`, { kind, name })
344
+ }
345
+ }
346
+ return [...found.values()].sort(
347
+ (a, b) => a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name),
348
+ )
349
+ }
350
+
351
+ // the `packages` block that replaces a config's old top-level wire slots: each
352
+ // name keyed by the direct dependency whose JavaScript asks for it. `packages`
353
+ // takes only 'noop' or a file path, so any other old value is named for a hand
354
+ // move instead of guessed.
355
+ export function wireSlotMigration(
356
+ appDir: string,
357
+ wire: { modules: Record<string, unknown>; views: Record<string, unknown> },
358
+ ): string {
359
+ const owners = new Map<string, string>()
360
+ const manifest = readProjectManifest(appDir)
361
+ for (const name of Object.keys({
362
+ ...manifest?.devDependencies,
363
+ ...manifest?.dependencies,
364
+ })) {
365
+ const dir = installedPackageDir(appDir, name)
366
+ if (!dir) continue
367
+ for (const request of packageNativeRequests(dir))
368
+ if (!owners.has(`${request.kind}:${request.name}`))
369
+ owners.set(`${request.kind}:${request.name}`, name)
370
+ }
371
+ // the app's own native code answers under its package name
372
+ if (typeof manifest?.name === 'string')
373
+ for (const request of packageNativeRequests(appDir))
374
+ if (!owners.has(`${request.kind}:${request.name}`))
375
+ owners.set(`${request.kind}:${request.name}`, manifest.name)
376
+ const unknownOwner = '<package that asks for it>'
377
+ const blocks = new Map<string, { modules: string[]; views: string[] }>()
378
+ const manual: string[] = []
379
+ for (const [kind, entries] of [
380
+ ['module', wire.modules],
381
+ ['view', wire.views],
382
+ ] as const) {
383
+ for (const [name, value] of Object.entries(entries)) {
384
+ const stub =
385
+ value === 'noop'
386
+ ? JSON.stringify(value)
387
+ : isRecord(value) &&
388
+ typeof value.file === 'string' &&
389
+ Object.keys(value).length === 1
390
+ ? JSON.stringify(value.file)
391
+ : null
392
+ if (!stub) {
393
+ manual.push(
394
+ `${name}: packages takes 'noop' or a file path; move this ${kind} into a file that default-exports it and name that file`,
395
+ )
396
+ continue
397
+ }
398
+ const owner = owners.get(`${kind}:${name}`) ?? unknownOwner
399
+ const block = blocks.get(owner) ?? { modules: [], views: [] }
400
+ block[kind === 'module' ? 'modules' : 'views'].push(
401
+ `[${JSON.stringify(name)}]: ${stub}`,
402
+ )
403
+ blocks.set(owner, block)
404
+ }
405
+ }
406
+ const lines = [...blocks].map(([owner, { modules, views }]) => {
407
+ const parts = [
408
+ modules.length ? `modules: { ${modules.join(', ')} }` : '',
409
+ views.length ? `views: { ${views.join(', ')} }` : '',
410
+ ].filter(Boolean)
411
+ return ` [${JSON.stringify(owner)}]: { ${parts.join(', ')} },`
412
+ })
413
+ return [
414
+ ...(lines.length ? [' packages: {', ...lines, ' },'] : []),
415
+ ...manual.map((line) => ` // ${line}`),
416
+ ].join('\n')
417
+ }
418
+
419
+ export function listDir(dir: string): string[] {
420
+ try {
421
+ return readdirSync(dir)
422
+ } catch {
423
+ return []
424
+ }
425
+ }
426
+
427
+ export function readSmallFile(file: string): string | null {
428
+ try {
429
+ return statSync(file).size <= 1024 * 1024 ? readFileSync(file, 'utf8') : null
430
+ } catch {
431
+ return null
432
+ }
433
+ }
package/cli/bin.ts CHANGED
@@ -13,6 +13,15 @@
13
13
  import { exitWithFlushedOutput } from '../src/process-output'
14
14
  import { RNX_FORKED_SCRIPT_ENV, RNX_INTERNAL_COMMAND } from './internal-child'
15
15
 
16
+ // a consumer such as head may close its pipe before the command finishes.
17
+ // exit directly: reporting this through stderr can fail on the same pipe.
18
+ for (const stream of [process.stdout, process.stderr]) {
19
+ stream.on('error', (error: NodeJS.ErrnoException) => {
20
+ if (error.code === 'EPIPE') process.exit(1)
21
+ throw error
22
+ })
23
+ }
24
+
16
25
  if (process.argv[2] === '__shell-init') {
17
26
  const { renderRnxShellInit } = await import('./shell-init')
18
27
  try {
@@ -53,9 +62,13 @@ if (process.argv[2] === '__cloud-confirm') {
53
62
  // work does. so this is the branch that must skip the CLI rather than the CLI
54
63
  // that must skip it.
55
64
  const forkedScript = process.argv[2]
56
- if (process.env[RNX_FORKED_SCRIPT_ENV] === '1' && forkedScript?.endsWith('.js')) {
65
+ if (
66
+ process.env[RNX_FORKED_SCRIPT_ENV] === '1' &&
67
+ forkedScript &&
68
+ /\.(?:c|m)?js$/.test(forkedScript)
69
+ ) {
57
70
  const { runScript } = await import('./internal-child')
58
- runScript(forkedScript, process.argv.slice(3))
71
+ await runScript(forkedScript, process.argv.slice(3))
59
72
  } else if (process.argv[2] === RNX_INTERNAL_COMMAND) {
60
73
  const { runInternalChild } = await import('./internal-child')
61
74
  await runInternalChild(process.argv.slice(3))