peachsim 0.1.577 → 0.1.579

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 (178) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +11 -9
  3. package/cli/app-catalog.ts +77 -0
  4. package/cli/app-fonts.ts +4 -20
  5. package/cli/app-splash.ts +2 -18
  6. package/cli/bridge-flow-runner.ts +505 -139
  7. package/cli/cloud-client.ts +1 -4
  8. package/cli/cloud-session.ts +1 -4
  9. package/cli/cloud-test-result-files.ts +3 -0
  10. package/cli/cloud-test-tunnel.ts +2 -5
  11. package/cli/command-registry.ts +2 -0
  12. package/cli/commands/agent.ts +149 -23
  13. package/cli/commands/assert.ts +1 -4
  14. package/cli/commands/box/peach-command.ts +62 -0
  15. package/cli/commands/box/plane-share.ts +0 -70
  16. package/cli/commands/box/rnx-command.ts +1 -60
  17. package/cli/commands/box/search-command.ts +14 -0
  18. package/cli/commands/box.ts +2 -2
  19. package/cli/commands/control.ts +121 -27
  20. package/cli/commands/cpu-profile.ts +23 -10
  21. package/cli/commands/daemon.ts +1 -9
  22. package/cli/commands/detox.ts +26 -18
  23. package/cli/commands/device.ts +129 -1
  24. package/cli/commands/film.ts +25 -16
  25. package/cli/commands/flow.ts +9 -2
  26. package/cli/commands/inspect/actions.ts +75 -1
  27. package/cli/commands/inspect/core.ts +81 -2
  28. package/cli/commands/inspect/describe.ts +2 -6
  29. package/cli/commands/inspect/env.ts +1 -1
  30. package/cli/commands/inspect/settling.ts +20 -3
  31. package/cli/commands/inspect/shared.ts +1 -4
  32. package/cli/commands/inspect/sleep.ts +1 -1
  33. package/cli/commands/inspect.ts +16 -49
  34. package/cli/commands/install-desktop.ts +8 -6
  35. package/cli/commands/login.ts +3 -5
  36. package/cli/commands/maestro-generate.ts +1 -4
  37. package/cli/commands/maestro.ts +38 -21
  38. package/cli/commands/no-bridge-hint.ts +0 -14
  39. package/cli/commands/permissions.ts +2 -2
  40. package/cli/commands/platform.ts +13 -6
  41. package/cli/commands/screenshot.ts +6 -1
  42. package/cli/commands/skills.ts +1 -1
  43. package/cli/commands/test.ts +6 -2
  44. package/cli/commands/three-mode.ts +162 -19
  45. package/cli/commands/upload.ts +3 -18
  46. package/cli/current-sim.ts +0 -1
  47. package/cli/desktop-companion.ts +40 -8
  48. package/cli/drivers/playwright-provisioning.ts +1 -4
  49. package/cli/flow-export.ts +1 -8
  50. package/cli/flow-session.ts +0 -4
  51. package/cli/hints.ts +1 -16
  52. package/cli/inspect-notice-state.ts +0 -4
  53. package/cli/main.ts +2 -4
  54. package/cli/outbound-endpoints.ts +1 -24
  55. package/cli/parse-args.ts +1 -2
  56. package/cli/rnx-in-process.ts +3 -1
  57. package/cli/run-rnx-core.ts +4 -2
  58. package/cli/run-rnx.ts +18 -10
  59. package/cli/send-to-box.ts +1 -4
  60. package/cli/telemetry.ts +1 -1
  61. package/cli/ws-bridge.ts +1 -5
  62. package/detox/gestures.ts +20 -6
  63. package/detox/index.ts +379 -123
  64. package/detox/navigation-lifecycle.ts +22 -13
  65. package/dist-lib/agent-daemon-client.cjs +21 -15
  66. package/dist-lib/agent-events.cjs +1 -1
  67. package/dist-lib/agent-identity.cjs +3 -17
  68. package/dist-lib/agent-sessions.cjs +5 -4
  69. package/dist-lib/attached-projects.cjs +1 -77
  70. package/dist-lib/auth/shared-session.cjs +1 -1
  71. package/dist-lib/backend-origin.cjs +11 -1
  72. package/dist-lib/beta.cjs +1 -1
  73. package/dist-lib/beta.mjs +1 -1
  74. package/dist-lib/bridge-constants.cjs +1 -1
  75. package/dist-lib/bridge-contract-input.cjs +7 -5
  76. package/dist-lib/bridge-contract-input.mjs +7 -5
  77. package/dist-lib/bridge-contract.cjs +1 -1
  78. package/dist-lib/bridge-contract.mjs +1 -1
  79. package/dist-lib/capture-contract.cjs +30 -7
  80. package/dist-lib/capture-contract.mjs +30 -7
  81. package/dist-lib/cli-constants.cjs +1 -1
  82. package/dist-lib/cloud-contract.cjs +1 -1
  83. package/dist-lib/cloud-contract.mjs +1 -1
  84. package/dist-lib/cloud-test-args.cjs +1 -1
  85. package/dist-lib/cloud-test-args.mjs +1 -1
  86. package/dist-lib/cloud.cjs +17 -11
  87. package/dist-lib/cloud.mjs +17 -11
  88. package/dist-lib/config.cjs +1 -1
  89. package/dist-lib/detox/index.cjs +321 -122
  90. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  91. package/dist-lib/home-paths.cjs +1 -1
  92. package/dist-lib/host/bridge-host.cjs +427 -212
  93. package/dist-lib/host/fetch-proxy-handler.cjs +5 -28
  94. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  95. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  96. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  97. package/dist-lib/host/websocket-proxy.cjs +1 -1
  98. package/dist-lib/index.cjs +315 -58
  99. package/dist-lib/jump-to-source-babel.cjs +1 -1
  100. package/dist-lib/jump-to-source-native.cjs +1 -1
  101. package/dist-lib/menu.cjs +67 -31
  102. package/dist-lib/menu.mjs +64 -28
  103. package/dist-lib/metro-fingerprint-registry.cjs +8 -4
  104. package/dist-lib/metro-fingerprint-registry.mjs +6 -4
  105. package/dist-lib/metro-production-bundle.cjs +2 -2
  106. package/dist-lib/metro-production-bundle.mjs +2 -2
  107. package/dist-lib/metro.cjs +8 -4
  108. package/dist-lib/profiles.cjs +1 -1
  109. package/dist-lib/public-brand.cjs +7 -4
  110. package/dist-lib/react-native-host-modules.cjs +1 -1
  111. package/dist-lib/react-native-host-modules.mjs +1 -1
  112. package/dist-lib/render-mode.cjs +3 -13
  113. package/dist-lib/scripts/dev-server-scanner.cjs +5 -17
  114. package/dist-lib/sdk.cjs +129 -10
  115. package/dist-lib/sdk.mjs +127 -10
  116. package/dist-lib/skills.cjs +1635 -1121
  117. package/dist-lib/swift-compile.cjs +277 -70
  118. package/dist-lib/swift-compile.mjs +275 -70
  119. package/dist-lib/swift-package.cjs +17 -5
  120. package/dist-lib/swift-package.mjs +15 -4
  121. package/dist-lib/swift-submit.cjs +32 -8
  122. package/dist-lib/swift-submit.mjs +29 -7
  123. package/dist-lib/swift.cjs +30912 -577
  124. package/dist-lib/vite-kotlin.cjs +356 -0
  125. package/dist-lib/vite-swift.cjs +1743 -0
  126. package/dist-lib/vite.cjs +208 -1039
  127. package/kotlin/README.md +18 -0
  128. package/kotlin/Sources/androidx/annotation/Keep.kt +4 -0
  129. package/kotlin/Sources/dev/onejs/one/OneNativeSourceDispatch.kt +7 -0
  130. package/kotlin/Sources/dev/onejs/one/source/OneModule.kt +4 -0
  131. package/kotlin/Sources/org/json/JSON.kt +244 -0
  132. package/kotlin/toolchain.json +19 -0
  133. package/package.json +14 -2
  134. package/scripts/dev-server-scanner.ts +0 -18
  135. package/scripts/optional-demo-registry.ts +0 -17
  136. package/skills/contrast/SKILL.md +2 -2
  137. package/skills/peach-setup/SKILL.md +16 -0
  138. package/src/agent-daemon-client.ts +36 -22
  139. package/src/agent-identity.ts +0 -15
  140. package/src/agent-prompt.ts +12 -0
  141. package/src/attached-projects.ts +0 -72
  142. package/src/backend-origin.ts +10 -0
  143. package/src/bridge-contract-input.ts +2 -5
  144. package/src/bridge-contract.ts +2 -0
  145. package/src/capture-contract.ts +53 -11
  146. package/src/cloud.ts +4 -9
  147. package/src/dev-server-open.ts +0 -99
  148. package/src/home-paths.ts +2 -3
  149. package/src/host/agent-host.ts +82 -36
  150. package/src/host/bridge-host.ts +5 -11
  151. package/src/host/fetch-proxy-handler.ts +19 -43
  152. package/src/is-record.ts +4 -0
  153. package/src/menu.ts +82 -37
  154. package/src/metro-fingerprint-registry.ts +1 -4
  155. package/src/metro-production-bundle.ts +1 -1
  156. package/src/plist.ts +1 -4
  157. package/src/public-brand.ts +5 -3
  158. package/src/render-mode.ts +0 -12
  159. package/src/runtime-assets.ts +9 -2
  160. package/src/screenshots/compose.ts +7 -1
  161. package/src/screenshots/frame-compose.ts +21 -10
  162. package/src/screenshots/schema.ts +1 -4
  163. package/src/sim-client.ts +2 -4
  164. package/src/skills/builtin/maestro-test.ts +1 -4
  165. package/src/sleep.ts +4 -0
  166. package/src/swift-compile.ts +209 -47
  167. package/src/swift-icu.ts +140 -0
  168. package/src/swift-package.ts +26 -2
  169. package/src/swift-submit.ts +84 -4
  170. package/src/swift.ts +2 -0
  171. package/src/vite-plugin-kotlin.ts +376 -0
  172. package/src/vite-plugin-one.ts +224 -36
  173. package/src/vite-plugin-swift.ts +382 -96
  174. package/src/vite-plugin.ts +121 -134
  175. package/src/vite.d.ts +10 -0
  176. package/cli/resolve-assets.ts +0 -63
  177. package/src/rolldown-plugin-swift.ts +0 -244
  178. package/src/team-machine-prompt.ts +0 -73
@@ -22,6 +22,7 @@ import {
22
22
  compatStubsForBuildResolver,
23
23
  reactNativeDeepStubsForBuildResolver,
24
24
  } from '../../peach-compat/src/stub-manifest.ts'
25
+ import { isExpoHostSocketModule } from '../../peach-engine/src/guest-module-overrides.ts'
25
26
  import { resolveSootsimBridgePort } from './bridge-constants.ts'
26
27
  import {
27
28
  SHELL_BRIDGE_IDENTITY_PATH,
@@ -58,6 +59,10 @@ const compatRoot = path.resolve(sootsimRoot, '..', 'peach-compat')
58
59
  // renames of either package don't break things.
59
60
  const engineRoot = path.resolve(workspaceRoot, 'packages/peach-engine')
60
61
  const rnShimPath = path.resolve(engineRoot, 'src/react-native/index.ts')
62
+ const rnAssetRegistryPath = path.resolve(
63
+ workspaceNodeModules,
64
+ 'react-native/src/asset-registry.js',
65
+ )
61
66
  const reactBridgePath = path.resolve(
62
67
  workspaceRoot,
63
68
  'packages/contrast-kit/src/react-bridge.ts',
@@ -81,6 +86,14 @@ const compatStubsDir = path.resolve(compatRoot, 'src/stubs')
81
86
  const nativeAutoStubPath = path.resolve(compatStubsDir, 'native-auto-stub.ts')
82
87
  const rnDeepStubDefault = path.resolve(compatStubsDir, 'react-native-internals.ts')
83
88
 
89
+ function hasExactFileName(candidate: string): boolean {
90
+ // a differently cased native file must not shadow a directory entry on macos.
91
+ return (
92
+ fs.existsSync(candidate) &&
93
+ fs.readdirSync(path.dirname(candidate)).includes(path.basename(candidate))
94
+ )
95
+ }
96
+
84
97
  function getInternalTamaguiPackages(): string[] {
85
98
  try {
86
99
  const scoped = fs
@@ -232,62 +245,6 @@ const expoUiJetpackComposeRealAliases = [
232
245
  }
233
246
  })
234
247
 
235
- // these components are upstream pure JS and should load unchanged over the
236
- // native seam. gesture handler 3 still supplies ReanimatedSwipeable but removed
237
- // DrawerLayout, so the latter resolves from the explicit v2 compatibility
238
- // alias. exact subpath aliases must precede the root facade alias.
239
- function rnghSubpathRealPath(packageName: string, subpath: string): string | null {
240
- try {
241
- const packageJsonPath = sootsimPluginRequire.resolve(
242
- `${packageName}/${subpath}/package.json`,
243
- )
244
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
245
- const moduleEntry = packageJson?.module
246
- if (typeof moduleEntry !== 'string' || moduleEntry.length === 0) return null
247
- const modulePath = path.resolve(path.dirname(packageJsonPath), moduleEntry)
248
- if (fs.existsSync(modulePath) && fs.statSync(modulePath).isFile()) return modulePath
249
- const jsPath = `${modulePath}.js`
250
- if (fs.existsSync(jsPath)) return jsPath
251
- const indexPath = path.join(modulePath, 'index.js')
252
- return fs.existsSync(indexPath) ? indexPath : null
253
- } catch {
254
- return null
255
- }
256
- }
257
-
258
- const rnghReanimatedSwipeableRealPath = rnghSubpathRealPath(
259
- 'react-native-gesture-handler',
260
- 'ReanimatedSwipeable',
261
- )
262
- const rnghDrawerLayoutRealPath = rnghSubpathRealPath(
263
- 'react-native-gesture-handler-v2',
264
- 'DrawerLayout',
265
- )
266
-
267
- const rnghGestureHandlerRootViewContextRealPath = (() => {
268
- try {
269
- return path.join(
270
- path.dirname(
271
- sootsimPluginRequire.resolve('react-native-gesture-handler/package.json'),
272
- ),
273
- 'lib/module/GestureHandlerRootViewContext.js',
274
- )
275
- } catch {
276
- return null
277
- }
278
- })()
279
- const rnghGestureHandlerRootViewContextShimPath = path.resolve(
280
- compatStubsDir,
281
- 'rngh-gesture-handler-root-view-context.ts',
282
- )
283
- const rnghRootStubPath = path.resolve(compatStubsDir, 'react-native-gesture-handler.ts')
284
- const rnghLibModuleRoots = [
285
- rnghGestureHandlerRootViewContextRealPath
286
- ? path.dirname(rnghGestureHandlerRootViewContextRealPath)
287
- : null,
288
- rnghDrawerLayoutRealPath ? path.dirname(path.dirname(rnghDrawerLayoutRealPath)) : null,
289
- ].filter((root): root is string => root !== null)
290
-
291
248
  const compatWrapperRealPackageAliases: Array<{
292
249
  find: string
293
250
  replacement: string
@@ -324,7 +281,6 @@ const coreOwnedPackages = [
324
281
  'react/jsx-dev-runtime',
325
282
  'canvaskit-wasm',
326
283
  'canvaskit-wasm/full',
327
- 'yoga-layout',
328
284
  ]
329
285
 
330
286
  const nativePackagePatterns = [
@@ -385,7 +341,7 @@ export function sootsim(options: SootSimOptions = {}): Plugin[] {
385
341
  },
386
342
  },
387
343
  sootsimConfigPlugin(appDir, extraSources, publishedBrowserPackages),
388
- rnghUpstreamInternalResolvePlugin(),
344
+ expoHostSocketBootstrapRedirect(),
389
345
  fixJsxRuntimeExports(),
390
346
  flowStripTransform(),
391
347
  nodeModulesJsxTransform(),
@@ -408,6 +364,27 @@ export function sootsim(options: SootSimOptions = {}): Plugin[] {
408
364
  ]
409
365
  }
410
366
 
367
+ function expoHostSocketBootstrapRedirect(): Plugin {
368
+ const target = '\0sootsim:expo-host-socket-bootstrap'
369
+ return {
370
+ name: 'sootsim-expo-host-socket-bootstrap',
371
+ enforce: 'pre',
372
+ resolveId(source, importer) {
373
+ if (isExpoHostSocketModule(stripViteQuery(source))) return target
374
+ if (!importer || !source.startsWith('.')) return null
375
+ const candidate = path.resolve(
376
+ path.dirname(stripViteQuery(importer)),
377
+ stripViteQuery(source),
378
+ )
379
+ return isExpoHostSocketModule(candidate) ? target : null
380
+ },
381
+ load(id) {
382
+ if (id !== target) return null
383
+ return 'export default function noopExpoHostSocketBootstrap() {}'
384
+ },
385
+ }
386
+ }
387
+
411
388
  function stripViteQuery(id: string): string {
412
389
  return id.split('?')[0]
413
390
  }
@@ -417,41 +394,6 @@ function isPathInside(child: string, parent: string): boolean {
417
394
  return rel === '' || (!!rel && !rel.startsWith('..') && !path.isAbsolute(rel))
418
395
  }
419
396
 
420
- function rnghUpstreamInternalResolvePlugin(): Plugin {
421
- return {
422
- name: 'sootsim-rngh-upstream-internal-resolve',
423
- enforce: 'pre',
424
- resolveId: {
425
- filter: {
426
- id: /(?:^\.\.\/\.\.(?:\.(?:js|jsx|mjs|ts|tsx))?$|GestureDetector(?:\.(?:js|jsx|mjs|ts|tsx))?$|GestureHandlerRootViewContext(?:\.(?:js|jsx|mjs|ts|tsx))?$|(?:PanGestureHandler|TapGestureHandler|State)(?:\.(?:js|jsx|mjs|ts|tsx))?$)/,
427
- },
428
- handler(source, importer) {
429
- if (!importer || rnghLibModuleRoots.length === 0) return null
430
- const importerPath = stripViteQuery(importer)
431
- if (!rnghLibModuleRoots.some((root) => isPathInside(importerPath, root))) {
432
- return null
433
- }
434
- const sourcePath = source.replace(/\.(js|jsx|mjs|ts|tsx)$/, '')
435
- if (
436
- sourcePath === '../..' ||
437
- sourcePath.endsWith('/handlers/gestures/GestureDetector') ||
438
- // upstream's DrawerLayout builds on the old-API handlers and the
439
- // State enum; both come from our seam, not upstream's native modules.
440
- sourcePath.endsWith('/handlers/PanGestureHandler') ||
441
- sourcePath.endsWith('/handlers/TapGestureHandler') ||
442
- sourcePath === '../State'
443
- ) {
444
- return rnghRootStubPath
445
- }
446
- if (sourcePath.endsWith('GestureHandlerRootViewContext')) {
447
- return rnghGestureHandlerRootViewContextShimPath
448
- }
449
- return null
450
- },
451
- },
452
- }
453
- }
454
-
455
397
  // shared worker plugin chain for the engine's shell-host build (used by
456
398
  // both `vite.config.ts` for dev/watch and `vite.build.config.ts` for prod).
457
399
  // keep these here — duplicating the list inline drifts (which is exactly how
@@ -499,7 +441,6 @@ export function engineShellWorkerPlugins(): Plugin[] {
499
441
  reanimatedNativeSeamRedirect(),
500
442
  manifestNativeSeamRedirect(),
501
443
  keyboardControllerBindingsRedirect(),
502
- rnghUpstreamInternalResolvePlugin(),
503
444
  workletsBabelTransform(() => true),
504
445
  workerBootChunkPlugin(ENGINE_WORKER_BOOT_CLOSURES),
505
446
  ]
@@ -523,27 +464,44 @@ export type WorkerBootClosure = {
523
464
  // substring of the worker entry module id this applies to
524
465
  entry: string
525
466
  // byte cap on the rendered worker file; fix excess boot dependencies and
526
- // unneeded inclusions rather than raising the cap
467
+ // unneeded inclusions rather than raising the cap.
468
+ // a cap failure in a worktree whose node_modules is symlinked from another
469
+ // tree is suspect: workspace modules resolve into the other tree and bundle
470
+ // twice (~1MB rendered here). re-run with a real `bun install` first.
527
471
  maxBytes: number
528
472
  }
529
473
 
530
474
  const ENGINE_WORKER_BOOT_CLOSURES: WorkerBootClosure[] = [
531
475
  // these workers inline their dynamic imports, so anything reachable from the
532
476
  // entry lands in the boot file whether or not it runs. that is why the font
533
- // parser is not reachable from either one: only the tenant reads a face's
477
+ // parser is not reachable from any of them: only the tenant reads a face's
534
478
  // name table, and it sends the aliases and metrics it read on the wire
535
479
  // (engine/app-font-registry.ts registerForwardedAppFonts). keep it that way
536
- // when adding font work here, or both caps take the parser again.
480
+ // when adding font work here, or every cap takes the parser again.
537
481
  // measured 3,406,869 bytes after the shell engine config split: the shell
538
482
  // worker installs a slim config without the list, modal, slider, swipeable,
539
483
  // and tab components nothing shell-side reads (compat-bridge composes the
540
484
  // full config for main, tenant, headless, and external realms on top of
541
485
  // it), and the gesture-handler stub imports TouchableNativeFeedback
542
486
  // statically instead of require()ing the whole react-native namespace into
543
- // the boot file.
544
- { entry: 'src/render-worker/shell-worker-entry.ts', maxBytes: 3_451_000 },
545
- { entry: 'src/render-worker/compositor-worker-entry.ts', maxBytes: 1_085_000 },
546
- { entry: 'src/render-worker/compositor-worker-ganesh-entry.ts', maxBytes: 1_820_000 },
487
+ // the boot file. the shell worker is one file per compositor backend, so
488
+ // each carries only its own CanvasKit glue: 3,263,311 graphite and
489
+ // 3,256,832 ganesh bytes when the split landed, down from 3,386,929 with both.
490
+ { entry: 'src/render-worker/shell-worker-entry.ts', maxBytes: 3_310_000 },
491
+ { entry: 'src/render-worker/shell-worker-ganesh-entry.ts', maxBytes: 3_300_000 },
492
+ { entry: 'src/render-worker/compositor-worker-entry.ts', maxBytes: 1_200_000 },
493
+ { entry: 'src/render-worker/compositor-worker-ganesh-entry.ts', maxBytes: 1_110_000 },
494
+ ]
495
+
496
+ // packages whose manifests omit `sideEffects` (metro never tree-shakes, so
497
+ // upstream has no reason to declare it), which makes rolldown keep every module
498
+ // their index reaches even when the worker reads none of it.
499
+ // @gorhom/bottom-sheet's index reaches BottomSheetFlashList, whose top-level
500
+ // optional require('@shopify/flash-list') put all of flash-list, plus the
501
+ // sheet's unused modal, list, and text input components, in the shell worker
502
+ // although the shell renders only the plain sheet: 67kB of the boot file.
503
+ const WORKER_SIDE_EFFECT_FREE_PACKAGES = [
504
+ { test: /\/node_modules\/@gorhom\/bottom-sheet\//, sideEffects: false },
547
505
  ]
548
506
 
549
507
  export function workerBootChunkPlugin(closures: WorkerBootClosure[]): Plugin {
@@ -557,6 +515,13 @@ export function workerBootChunkPlugin(closures: WorkerBootClosure[]): Plugin {
557
515
  typeof input === 'string'
558
516
  ? (closures.find((candidate) => input.includes(candidate.entry)) ?? null)
559
517
  : null
518
+ if (!closure) return null
519
+ const treeshake =
520
+ typeof inputOptions.treeshake === 'object' ? inputOptions.treeshake : {}
521
+ return {
522
+ ...inputOptions,
523
+ treeshake: { ...treeshake, moduleSideEffects: WORKER_SIDE_EFFECT_FREE_PACKAGES },
524
+ }
560
525
  },
561
526
  outputOptions(options) {
562
527
  if (!closure) return null
@@ -572,8 +537,33 @@ export function workerBootChunkPlugin(closures: WorkerBootClosure[]): Plugin {
572
537
  }
573
538
  const bytes = Buffer.byteLength(chunks[0].code)
574
539
  if (bytes > closure.maxBytes) {
540
+ const chunk = chunks[0]
541
+ const pkgs = new Map<string, Set<string>>()
542
+ for (const id of chunk.moduleIds) {
543
+ const nmIndex = id.lastIndexOf('/node_modules/')
544
+ if (nmIndex !== -1) {
545
+ const rest = id.slice(nmIndex + '/node_modules/'.length)
546
+ const pkgMatch = rest.match(/^(?:@[^/]+\/)?[^/]+/)
547
+ if (pkgMatch) {
548
+ const pkgName = pkgMatch[0]
549
+ if (!pkgs.has(pkgName)) pkgs.set(pkgName, new Set())
550
+ const baseDir = id.slice(
551
+ 0,
552
+ nmIndex + '/node_modules/'.length + pkgName.length,
553
+ )
554
+ pkgs.get(pkgName)!.add(baseDir)
555
+ }
556
+ }
557
+ }
558
+ const duplicates = [...pkgs.entries()]
559
+ .filter(([, dirs]) => dirs.size > 1)
560
+ .map(([name, dirs]) => `${name} (${[...dirs].join(', ')})`)
561
+ const dupDetail =
562
+ duplicates.length > 0
563
+ ? `\ndetected duplicate packages bundled from multiple roots (likely a symlinked or cross-tree node_modules):\n ${duplicates.join('\n ')}`
564
+ : ''
575
565
  this.error(
576
- `worker ${closure.entry}: ${chunks[0].fileName} is ${bytes} bytes, over the ${closure.maxBytes} byte cap; eliminate excess boot dependencies rather than raising the cap`,
566
+ `worker ${closure.entry}: ${chunks[0].fileName} is ${bytes} bytes, over the ${closure.maxBytes} byte cap; eliminate excess boot dependencies rather than raising the cap${dupDetail}`,
577
567
  )
578
568
  }
579
569
  },
@@ -610,13 +600,13 @@ export function keyboardControllerBindingsRedirect(): Plugin {
610
600
  for (const ext of nativeExts) {
611
601
  const candidate = base + ext
612
602
  try {
613
- if (fs.existsSync(candidate)) return candidate
603
+ if (hasExactFileName(candidate)) return candidate
614
604
  } catch {}
615
605
  }
616
606
  for (const ext of nativeExts) {
617
607
  const candidate = path.join(base, 'index' + ext)
618
608
  try {
619
- if (fs.existsSync(candidate)) return candidate
609
+ if (hasExactFileName(candidate)) return candidate
620
610
  } catch {}
621
611
  }
622
612
  return null
@@ -679,6 +669,7 @@ export function keyboardControllerBindingsRedirect(): Plugin {
679
669
  // vite's worker build. redirect the validate script to a no-op stub.
680
670
  const reanimatedNativeSeamFiles = new Set([
681
671
  'NativeReanimatedModule',
672
+ 'NativeWorkletsModule',
682
673
  'scrollTo',
683
674
  'measure',
684
675
  'setNativeProps',
@@ -700,7 +691,7 @@ export function reanimatedNativeSeamRedirect(): Plugin {
700
691
  enforce: 'pre',
701
692
  resolveId: {
702
693
  filter: {
703
- id: /(?:^react-native-worklets\/package\.json$|^react-native-reanimated\/scripts\/validate-worklets-version(?:\.js)?$|(?:^|\/)(?:fabricUtils|NativeReanimatedModule|scrollTo|measure|setNativeProps|dispatchCommand|setGestureState)(?:\.(?:native|web|ios|android))?$)/,
694
+ id: /(?:^react-native-worklets\/package\.json$|^react-native-reanimated\/scripts\/validate-worklets-version(?:\.js)?$|(?:^|\/)(?:fabricUtils|NativeReanimatedModule|NativeWorkletsModule|scrollTo|measure|setNativeProps|dispatchCommand|setGestureState)(?:\.(?:native|web|ios|android))?$)/,
704
695
  },
705
696
  handler(source, importer) {
706
697
  // upstream's `assertWorkletsVersion` (DEV-only) imports a build-time
@@ -738,6 +729,9 @@ export function reanimatedNativeSeamRedirect(): Plugin {
738
729
  return reanimatedFabricUtilsRedirectPath
739
730
  }
740
731
  if (!reanimatedNativeSeamFiles.has(basename)) return null
732
+ if (basename === 'NativeWorkletsModule') {
733
+ return path.resolve(compatStubsDir, 'react-native-worklets-native.ts')
734
+ }
741
735
  if (process.env.SOOTSIM_REANIMATED_REDIRECT_DEBUG) {
742
736
  console.log(
743
737
  '[reanimatedRedirect]',
@@ -1029,11 +1023,12 @@ function sootsimConfigPlugin(
1029
1023
  // their parent package (e.g. foo) — vite processes aliases in order
1030
1024
  .sort((a, b) => b[0].length - a[0].length)
1031
1025
  .map(([find, file]) => ({
1032
- // the bare expo entry is a facade while its package subpaths are
1033
- // independent upstream modules. a string alias is prefix-matched by
1034
- // vite, so make this one exact or expo/devtools and future subpaths
1035
- // become impossible paths such as expo.ts/devtools.
1036
- find: find === 'expo' ? /^expo$/ : find,
1026
+ // these stubs replace only the package root; their pure-js subpaths
1027
+ // (core's modules, screens' experimental Split) run upstream.
1028
+ find:
1029
+ find === 'expo-modules-core' || find === 'react-native-screens'
1030
+ ? new RegExp(`^${find}$`)
1031
+ : find,
1037
1032
  replacement: path.resolve(compatStubsDir, file),
1038
1033
  }))
1039
1034
 
@@ -1195,13 +1190,21 @@ function sootsimConfigPlugin(
1195
1190
  alias: [
1196
1191
  ...sootsimBrowserSourceAliases,
1197
1192
  ...publishedBrowserEntryAliases,
1193
+ {
1194
+ find: /^@react-native\/assets-registry\/registry$/,
1195
+ replacement: rnAssetRegistryPath,
1196
+ },
1198
1197
  ...Object.entries(rnLibraryStubs).map(([find, replacement]) => ({
1199
1198
  find,
1200
1199
  replacement,
1201
1200
  })),
1202
1201
  { find: /^react-native\/Libraries\/.*/, replacement: rnDeepStubDefault },
1203
- { find: 'react-native', replacement: rnShimPath },
1204
- { find: 'react-native-web', replacement: rnShimPath },
1202
+ {
1203
+ find: /^react-native\/asset-registry$/,
1204
+ replacement: rnAssetRegistryPath,
1205
+ },
1206
+ { find: /^react-native$/, replacement: rnShimPath },
1207
+ { find: /^react-native-web$/, replacement: rnShimPath },
1205
1208
  // resolved-path alias for the synthetic specifier the
1206
1209
  // @react-navigation/native compat wrapper imports from. terminates
1207
1210
  // the wrapper's own real-package import without re-entering the
@@ -1233,22 +1236,6 @@ function sootsimConfigPlugin(
1233
1236
  ]
1234
1237
  : []),
1235
1238
  ...expoUiJetpackComposeRealAliases,
1236
- ...(rnghReanimatedSwipeableRealPath
1237
- ? [
1238
- {
1239
- find: /^react-native-gesture-handler\/ReanimatedSwipeable$/,
1240
- replacement: rnghReanimatedSwipeableRealPath,
1241
- },
1242
- ]
1243
- : []),
1244
- ...(rnghDrawerLayoutRealPath
1245
- ? [
1246
- {
1247
- find: /^(?:react-native-gesture-handler|react-native-gesture-handler-v2)\/DrawerLayout$/,
1248
- replacement: rnghDrawerLayoutRealPath,
1249
- },
1250
- ]
1251
- : []),
1252
1239
  ...stubAliases,
1253
1240
  ],
1254
1241
  },
@@ -1439,13 +1426,13 @@ function sootsimEsbuildPlugin(appDir: string) {
1439
1426
  for (const ext of platformExts) {
1440
1427
  const candidate = resolved + ext
1441
1428
  try {
1442
- if (fs.existsSync(candidate)) return { path: candidate }
1429
+ if (hasExactFileName(candidate)) return { path: candidate }
1443
1430
  } catch {}
1444
1431
  }
1445
1432
  for (const ext of platformExts) {
1446
1433
  const candidate = path.join(resolved, 'index' + ext)
1447
1434
  try {
1448
- if (fs.existsSync(candidate)) return { path: candidate }
1435
+ if (hasExactFileName(candidate)) return { path: candidate }
1449
1436
  } catch {}
1450
1437
  }
1451
1438
  const allExts = [...platformExts, '.tsx', '.ts', '.jsx', '.js', '.mjs', '.cjs']
@@ -1455,7 +1442,7 @@ function sootsimEsbuildPlugin(appDir: string) {
1455
1442
  for (const pext of platformExts) {
1456
1443
  const candidate = base + pext
1457
1444
  try {
1458
- if (fs.existsSync(candidate)) return { path: candidate }
1445
+ if (hasExactFileName(candidate)) return { path: candidate }
1459
1446
  } catch {}
1460
1447
  }
1461
1448
  break
@@ -1614,7 +1601,7 @@ export function workletsBabelTransform(
1614
1601
  // pipeline; vite/rolldown/oxc do not. detect the `@flow` pragma in node_modules and
1615
1602
  // run @babel/plugin-transform-flow-strip-types so the wrapper-real seam can pull
1616
1603
  // in the real upstream JS without re-implementing it locally. mirrored in
1617
- // scripts/build-bundler-deps.ts so esbuild-based dep prebundles stay in sync.
1604
+ // scripts/build/bundler-deps.ts so esbuild-based dep prebundles stay in sync.
1618
1605
  let flowBabel: typeof import('@babel/core') | null = null
1619
1606
  let flowStripPlugin: import('@babel/core').PluginItem | null = null
1620
1607
  let jsxSyntaxPlugin: import('@babel/core').PluginItem | null = null
@@ -1698,13 +1685,13 @@ function metroNativeResolve(isAppSource: (p: string) => boolean): Plugin {
1698
1685
  for (const ext of platformExts) {
1699
1686
  const candidate = base + ext
1700
1687
  try {
1701
- if (fs.existsSync(candidate)) return candidate
1688
+ if (hasExactFileName(candidate)) return candidate
1702
1689
  } catch {}
1703
1690
  }
1704
1691
  for (const ext of platformExts) {
1705
1692
  const candidate = path.join(base, 'index' + ext)
1706
1693
  try {
1707
- if (fs.existsSync(candidate)) return candidate
1694
+ if (hasExactFileName(candidate)) return candidate
1708
1695
  } catch {}
1709
1696
  }
1710
1697
  return null
package/src/vite.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { Plugin } from 'vite'
2
+
3
+ export interface PeachPluginOptions {
4
+ bundleUrl?: string
5
+ prefix?: string
6
+ enabled?: boolean
7
+ open?: boolean | { appName: string }
8
+ }
9
+
10
+ export declare function rnxPlugin(options?: PeachPluginOptions): Plugin
@@ -1,63 +0,0 @@
1
- // resolve peach's bundled assets (fonts, icons, sounds)
2
- // regardless of how the package was installed (source checkout, npm install, bunx).
3
- //
4
- // uses import.meta.url to derive the package root, then checks both
5
- // the source layout (public/) and npm-installed layout (public/).
6
-
7
- import { existsSync, readFileSync } from 'fs'
8
- import { dirname, resolve } from 'path'
9
- import { fileURLToPath } from 'url'
10
- import { rnxPublicBrand } from '../src/public-brand'
11
-
12
- // when bundled by esbuild, this file lives at dist-cli/bin.js
13
- // when running from source, this file lives at cli/resolve-assets.ts
14
- const thisDir = dirname(fileURLToPath(import.meta.url))
15
-
16
- // walk up to find package root: look for package.json with the peachsim package name
17
- function findPackageRoot(): string {
18
- let dir = thisDir
19
- for (let i = 0; i < 5; i++) {
20
- try {
21
- const pkgPath = resolve(dir, 'package.json')
22
- if (existsSync(pkgPath)) {
23
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
24
- if (pkg.name === rnxPublicBrand.packageName) return dir
25
- }
26
- } catch {}
27
- dir = dirname(dir)
28
- }
29
- // fallback: assume one level up from this file
30
- return dirname(thisDir)
31
- }
32
-
33
- const packageRoot = findPackageRoot()
34
-
35
- export interface SootSimAssets {
36
- fontsDir: string
37
- iconsDir: string
38
- soundsDir: string
39
- packageRoot: string
40
- }
41
-
42
- export function resolveAssets(): SootSimAssets {
43
- // both source and npm-installed layouts use public/ at the package root
44
- const publicDir = resolve(packageRoot, 'public')
45
-
46
- const fontsDir = resolve(publicDir, 'fonts')
47
- const iconsDir = resolve(publicDir, 'icons')
48
- const soundsDir = resolve(publicDir, 'sounds')
49
-
50
- if (!existsSync(fontsDir)) {
51
- console.warn(`[peach] warning: fonts directory not found at ${fontsDir}`)
52
- }
53
-
54
- return {
55
- fontsDir,
56
- iconsDir,
57
- soundsDir,
58
- packageRoot,
59
- }
60
- }
61
-
62
- // re-export packageRoot for use in CLI commands that need SOOTSIM_ROOT
63
- export { packageRoot }