peachsim 0.1.2198 → 0.1.2200

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 (135) hide show
  1. package/cli/commands/inspect/settling.ts +20 -20
  2. package/cli/outbound-endpoints.ts +12 -0
  3. package/dist-lib/agent-daemon-client.cjs +1 -1
  4. package/dist-lib/agent-events.cjs +1 -1
  5. package/dist-lib/attached-projects.cjs +1 -1
  6. package/dist-lib/auth/shared-session.cjs +1 -1
  7. package/dist-lib/backend-origin.cjs +1 -1
  8. package/dist-lib/beta.cjs +1 -1
  9. package/dist-lib/beta.mjs +1 -1
  10. package/dist-lib/bridge-constants.cjs +1 -1
  11. package/dist-lib/bridge-contract-input.cjs +1 -1
  12. package/dist-lib/bridge-contract-input.mjs +1 -1
  13. package/dist-lib/bridge-contract.cjs +1 -1
  14. package/dist-lib/bridge-contract.mjs +1 -1
  15. package/dist-lib/bundle-channel.cjs +1532 -1170
  16. package/dist-lib/capture-contract.cjs +1 -1
  17. package/dist-lib/capture-contract.mjs +1 -1
  18. package/dist-lib/cloud-contract.cjs +1 -1
  19. package/dist-lib/cloud-contract.mjs +1 -1
  20. package/dist-lib/cloud-test-args.cjs +1 -1
  21. package/dist-lib/cloud-test-args.mjs +1 -1
  22. package/dist-lib/cloud.cjs +1 -1
  23. package/dist-lib/cloud.mjs +1 -1
  24. package/dist-lib/config.cjs +1 -1
  25. package/dist-lib/config.mjs +1 -1
  26. package/dist-lib/detox/index.cjs +22 -22
  27. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  28. package/dist-lib/expo-swift-bundle.cjs +45875 -39899
  29. package/dist-lib/expo-swift-metadata.cjs +1 -1
  30. package/dist-lib/expo-swift-metadata.mjs +1 -1
  31. package/dist-lib/home-paths.cjs +1 -1
  32. package/dist-lib/host/bridge-host.cjs +50942 -49780
  33. package/dist-lib/host/fetch-proxy-handler.cjs +16 -4
  34. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  35. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  36. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  37. package/dist-lib/host/websocket-proxy.cjs +1 -1
  38. package/dist-lib/index.cjs +36 -24
  39. package/dist-lib/jump-to-source-babel.cjs +1 -1
  40. package/dist-lib/jump-to-source-native.cjs +1 -1
  41. package/dist-lib/kotlin-host.cjs +100 -10
  42. package/dist-lib/menu.cjs +1 -1
  43. package/dist-lib/menu.mjs +1 -1
  44. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  45. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  46. package/dist-lib/metro-production-bundle.cjs +1 -1
  47. package/dist-lib/metro-production-bundle.mjs +1 -1
  48. package/dist-lib/metro.cjs +16 -4
  49. package/dist-lib/native-app-identity-project.cjs +14 -2
  50. package/dist-lib/native-app-identity-project.mjs +14 -2
  51. package/dist-lib/native-app-identity.cjs +1 -1
  52. package/dist-lib/native-app-identity.mjs +1 -1
  53. package/dist-lib/native-firebase-metadata.cjs +1 -1
  54. package/dist-lib/native-firebase-metadata.mjs +1 -1
  55. package/dist-lib/pdfium-raster.cjs +1 -1
  56. package/dist-lib/pdfium-raster.mjs +1 -1
  57. package/dist-lib/plist.cjs +1 -1
  58. package/dist-lib/plist.mjs +1 -1
  59. package/dist-lib/profiles.cjs +1 -1
  60. package/dist-lib/public-brand.cjs +1 -1
  61. package/dist-lib/public-origins-lib-esm.json +24 -24
  62. package/dist-lib/public-origins-lib.json +302 -289
  63. package/dist-lib/react-native-host-modules.cjs +1 -1
  64. package/dist-lib/react-native-host-modules.mjs +1 -1
  65. package/dist-lib/reap-browser.cjs +1 -1
  66. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  67. package/dist-lib/sdk.cjs +21 -21
  68. package/dist-lib/sdk.mjs +21 -21
  69. package/dist-lib/skills.cjs +3622 -2962
  70. package/dist-lib/swift-compile.cjs +34 -3
  71. package/dist-lib/swift-compile.mjs +34 -3
  72. package/dist-lib/swift-package.cjs +1 -1
  73. package/dist-lib/swift-package.mjs +1 -1
  74. package/dist-lib/swift-submit.cjs +1 -1
  75. package/dist-lib/swift-submit.mjs +1 -1
  76. package/dist-lib/swift-toolchain.cjs +1 -1
  77. package/dist-lib/swift.cjs +1 -1
  78. package/dist-lib/vite-swift.cjs +129 -43
  79. package/dist-lib/vite.cjs +16 -4
  80. package/dist-lib/worklet-executable.cjs +1 -1
  81. package/dist-lib/worklet-executable.mjs +1 -1
  82. package/dist-types/packages/peach-engine/src/engine/scene-table.d.ts +2 -0
  83. package/dist-types/packages/peach-engine/src/render-worker/app-prefetch-contract.d.ts +5 -0
  84. package/dist-types/packages/peach-engine/src/render-worker/protocol.d.ts +2 -0
  85. package/dist-types/packages/peach-engine/src/render-worker/tree-sync-port.d.ts +5 -0
  86. package/kotlin/sdk.tar.gz +0 -0
  87. package/package.json +1 -1
  88. package/runtime-companion.json +1 -1
  89. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h +2 -0
  90. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSBundle.h +1 -1
  91. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSByteCountFormatter.h +1 -1
  92. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSCharacterSet.h +1 -1
  93. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSCompoundPredicate.h +1 -1
  94. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSDateIntervalFormatter.h +1 -1
  95. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSDecimalNumber.h +1 -1
  96. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSEnergyFormatter.h +1 -1
  97. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSExpression.h +1 -1
  98. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSFileHandle.h +2 -2
  99. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSISO8601DateFormatter.h +1 -1
  100. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSIndexSet.h +1 -1
  101. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSLengthFormatter.h +1 -1
  102. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSLock.h +3 -3
  103. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSMassFormatter.h +1 -1
  104. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSMeasurementFormatter.h +1 -1
  105. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSNotification.h +1 -1
  106. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSNotificationQueue.h +1 -1
  107. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSObjCRuntime.h +4 -0
  108. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSOrthography.h +1 -1
  109. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSScanner.h +1 -1
  110. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSThread.h +1 -1
  111. package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSUnit.h +25 -25
  112. package/sdk/ios/System/Library/Frameworks/QuartzCore.framework/Headers/CAValueFunction.h +1 -1
  113. package/sdk/ios/System/Library/Frameworks/QuartzCore.framework/Headers/CoreAnimation.h +1 -0
  114. package/sdk/ios/System/Library/Frameworks/QuartzCore.framework/Modules/module.modulemap +1 -0
  115. package/sdk/ios/System/Library/Frameworks/UIKitText.framework/Headers/UIKitText.apinotes +14 -0
  116. package/src/android-layout-ids.ts +50 -0
  117. package/src/android-view-classes.ts +41 -0
  118. package/src/expo-native-workspace.ts +2 -3
  119. package/src/expo-swift-host.ts +6 -1
  120. package/src/java-module-abi.ts +2 -0
  121. package/src/javaAbiContract.ts +2 -0
  122. package/src/kotlin-app.ts +33 -7
  123. package/src/kotlin-host.ts +83 -6
  124. package/src/kotlin-maven-sources.ts +248 -0
  125. package/src/native-prebuilt.ts +36 -2
  126. package/src/native-swift-slices.ts +43 -2
  127. package/src/react-native-bridge-modules.ts +7 -1
  128. package/src/react-native-core-build.ts +176 -0
  129. package/src/react-native-core-digest.ts +1 -1
  130. package/src/react-native-core.ts +24 -7
  131. package/src/swift-packages.ts +3 -0
  132. package/src/swift-standin-emit.ts +9 -1
  133. package/src/swift-standins.ts +5 -2
  134. package/src/swift-wasm-prune.ts +41 -3
  135. package/src/turbo-compile.ts +2 -1
@@ -14,6 +14,7 @@
14
14
  #import <QuartzCore/CAEmitterCell.h>
15
15
  #import <QuartzCore/CAEmitterLayer.h>
16
16
  #import <QuartzCore/CAGradientLayer.h>
17
+ #import <QuartzCore/CAMetalLayer.h>
17
18
  #import <QuartzCore/CAReplicatorLayer.h>
18
19
  #import <QuartzCore/CAScrollLayer.h>
19
20
  #import <QuartzCore/CAShapeLayer.h>
@@ -8,5 +8,6 @@ framework module QuartzCore [system] {
8
8
  exclude header "CAFrameRateRange.h"
9
9
  exclude header "CATransform3D.h"
10
10
  exclude header "CABase.h"
11
+ exclude header "CAValueFunction.h"
11
12
  export *
12
13
  }
@@ -81,6 +81,20 @@ Tags:
81
81
  Typedefs:
82
82
  - Name: NSTextListMarkerFormat
83
83
  SwiftName: NSTextList.MarkerFormat
84
+ - Name: NSAttributedStringDocumentAttributeKey
85
+ SwiftName: NSAttributedString.DocumentAttributeKey
86
+ - Name: NSAttributedStringDocumentReadingOptionKey
87
+ SwiftName: NSAttributedString.DocumentReadingOptionKey
88
+ - Name: NSAttributedStringDocumentType
89
+ SwiftName: NSAttributedString.DocumentType
90
+ - Name: NSTextEffectStyle
91
+ SwiftName: NSAttributedString.TextEffectStyle
92
+ - Name: NSTextHighlightStyle
93
+ SwiftName: NSAttributedString.TextHighlightStyle
94
+ - Name: NSTextHighlightColorScheme
95
+ SwiftName: NSAttributedString.TextHighlightColorScheme
96
+ - Name: NSTextLayoutSectionKey
97
+ SwiftName: NSAttributedString.TextLayoutSectionKey
84
98
  - Name: NSTextTabOptionKey
85
99
  SwiftName: NSTextTab.OptionKey
86
100
  - Name: UIFontDescriptorAttributeName
@@ -0,0 +1,50 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { parseXml, type XmlElement } from './android-xml'
4
+
5
+ // the layout names and view ids a module's R class declares: every layout
6
+ // file's name, every `@+id/` or `@id/` an attribute names in a layout, and each
7
+ // `<item type="id">` in values, as aapt2 declares them
8
+ export interface AndroidLayoutIds {
9
+ layouts: string[]
10
+ ids: string[]
11
+ // the unqualified `layout` directory's files by name, which Resources inflates
12
+ elements: Map<string, XmlElement>
13
+ }
14
+
15
+ const LAYOUT_DIR = /^layout(?:-[A-Za-z0-9+-]+)?$/
16
+ const ID_REFERENCE = /@\+?id\/([A-Za-z0-9_.]+)/g
17
+ const ID_ITEM = /<item\b[^>]*\btype\s*=\s*"id"[^>]*>/g
18
+ const ITEM_NAME = /\bname\s*=\s*"([^"]+)"/
19
+
20
+ export function readAndroidLayoutIds(moduleDir: string): AndroidLayoutIds {
21
+ const res = path.join(moduleDir, 'src/main/res')
22
+ const layouts = new Set<string>()
23
+ const ids = new Set<string>()
24
+ const elements = new Map<string, XmlElement>()
25
+ for (const dir of fs.existsSync(res) ? fs.readdirSync(res).sort() : []) {
26
+ const directory = path.join(res, dir)
27
+ if (LAYOUT_DIR.test(dir)) {
28
+ for (const entry of fs.readdirSync(directory).sort()) {
29
+ if (!entry.endsWith('.xml')) continue
30
+ layouts.add(entry.slice(0, -'.xml'.length))
31
+ const xml = fs.readFileSync(path.join(directory, entry), 'utf8')
32
+ for (const match of xml.matchAll(ID_REFERENCE)) ids.add(match[1])
33
+ if (dir === 'layout') {
34
+ const root = parseXml(xml, path.join(directory, entry)).children[0]
35
+ if (root) elements.set(entry.slice(0, -'.xml'.length), root)
36
+ }
37
+ }
38
+ } else if (dir.startsWith('values')) {
39
+ for (const entry of fs.readdirSync(directory).sort()) {
40
+ if (!entry.endsWith('.xml')) continue
41
+ const xml = fs.readFileSync(path.join(directory, entry), 'utf8')
42
+ for (const item of xml.matchAll(ID_ITEM)) {
43
+ const name = ITEM_NAME.exec(item[0])?.[1]
44
+ if (name) ids.add(name)
45
+ }
46
+ }
47
+ }
48
+ }
49
+ return { layouts: [...layouts].sort(), ids: [...ids].sort(), elements }
50
+ }
@@ -0,0 +1,41 @@
1
+ import type { XmlElement } from './android-xml'
2
+
3
+ // the framework view classes LayoutInflater can create; each is a Kotlin class
4
+ // of the same name in AndroidViewSources, linked into an app only when one of
5
+ // its layouts names it
6
+ export const androidViewClasses = [
7
+ 'android.view.View',
8
+ 'android.widget.FrameLayout',
9
+ 'android.widget.ImageView',
10
+ 'android.widget.LinearLayout',
11
+ 'android.widget.RelativeLayout',
12
+ 'android.widget.TextView',
13
+ ]
14
+
15
+ // PhoneLayoutInflater's prefixes, then LayoutInflater's own
16
+ const classPrefixes = [
17
+ 'android.widget.',
18
+ 'android.webkit.',
19
+ 'android.app.',
20
+ 'android.view.',
21
+ ]
22
+
23
+ // the classes a set of layout trees names, as the inflater resolves a tag: a
24
+ // dotted name is that class, any other tries each framework prefix in order
25
+ export function layoutViewClasses(roots: Iterable<XmlElement>): string[] {
26
+ const used = new Set<string>()
27
+ const visit = (element: XmlElement) => {
28
+ if (element.name !== 'include' && element.name !== 'merge') {
29
+ if (element.name.includes('.')) used.add(element.name)
30
+ else {
31
+ const found = classPrefixes
32
+ .map((prefix) => prefix + element.name)
33
+ .find((name) => androidViewClasses.includes(name))
34
+ if (found) used.add(found)
35
+ }
36
+ }
37
+ element.children.forEach(visit)
38
+ }
39
+ for (const root of roots) visit(root)
40
+ return [...used].sort()
41
+ }
@@ -744,7 +744,7 @@ function trackedSourceEntries(root: string, work: string): string[] {
744
744
  function initializeWorkspaceGit(root: string, work: string) {
745
745
  requireNativeGit()
746
746
  const env = { ...process.env, ...isolatedGitEnv }
747
- execFileSync('git', ['init', '--quiet'], { cwd: work, env, timeout: 60_000 })
747
+ execFileSync('git', ['init', '--quiet'], { cwd: work, env })
748
748
  execFileSync(
749
749
  'git',
750
750
  [
@@ -761,7 +761,7 @@ function initializeWorkspaceGit(root: string, work: string) {
761
761
  '-m',
762
762
  'prepared workspace',
763
763
  ],
764
- { cwd: work, env, timeout: 60_000 },
764
+ { cwd: work, env },
765
765
  )
766
766
  const tracked = trackedSourceEntries(root, work)
767
767
  if (tracked.length > 0)
@@ -769,7 +769,6 @@ function initializeWorkspaceGit(root: string, work: string) {
769
769
  cwd: work,
770
770
  env,
771
771
  input: tracked.join('\0'),
772
- timeout: 60_000,
773
772
  })
774
773
  }
775
774
 
@@ -2353,7 +2353,12 @@ async function compileExpoSwiftModulesTraced(
2353
2353
  'react-native/package.json',
2354
2354
  ),
2355
2355
  )
2356
- const provider = prebuiltReactNativeCore(reactNativeRoot, module.root, 'expo')
2356
+ const provider = await prebuiltReactNativeCore(
2357
+ projectRoot,
2358
+ reactNativeRoot,
2359
+ module.root,
2360
+ 'expo',
2361
+ )
2357
2362
  const headers = new Map<string, { file: string; relative: string }[]>()
2358
2363
  for (const [file, body] of Object.entries(provider.files)) {
2359
2364
  const relative = file.split('/upstream/react-native/')[1]
@@ -62,6 +62,7 @@ export async function readJavaModuleSpecs(
62
62
  typeof row.className !== 'string' ||
63
63
  (row.name !== null && typeof row.name !== 'string') ||
64
64
  typeof row.source !== 'string' ||
65
+ (row.application !== undefined && typeof row.application !== 'boolean') ||
65
66
  !Array.isArray(row.methods)
66
67
  )
67
68
  throw new Error('Java module reader printed an invalid spec')
@@ -73,6 +74,7 @@ export async function readJavaModuleSpecs(
73
74
  name: row.name,
74
75
  source: row.source,
75
76
  methods: row.methods.map(readBridgeMethod),
77
+ application: row.application === true,
76
78
  }
77
79
  })
78
80
  return { sha256: digest.digest('hex'), specs }
@@ -6,6 +6,8 @@ export interface JavaModuleSpec {
6
6
  name: string | null
7
7
  source: string
8
8
  methods: BridgeMethod[]
9
+ // compiled with the application: it names classes only the application defines
10
+ application: boolean
9
11
  }
10
12
 
11
13
  export interface JavaModuleSpecs {
package/src/kotlin-app.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  import fs from 'node:fs'
7
7
  import path from 'node:path'
8
8
  import { readAndroidDrawables, type AndroidDrawable } from './android-drawables'
9
+ import { readAndroidLayoutIds, type AndroidLayoutIds } from './android-layout-ids'
9
10
  import { readAndroidStrings, type AndroidStringResources } from './android-strings'
10
11
  import {
11
12
  readResourceValues,
@@ -18,6 +19,7 @@ import { parseXml, type XmlElement } from './android-xml'
18
19
  import frameworkThemes from './generated/android-framework-themes.json' with { type: 'json' }
19
20
  import { isRecord } from './is-record'
20
21
  import { composeBomVersions } from './kotlin-compose-bom'
22
+ import { mavenKotlinSources } from './kotlin-maven-sources'
21
23
  import { KotlinVersionCatalog } from './kotlin-version-catalog'
22
24
  import type { AndroidGradleDependency } from './androidGradleContract'
23
25
 
@@ -60,6 +62,7 @@ export interface KotlinAppProject {
60
62
  strings: AndroidStringResources
61
63
  colors: KotlinAppColor[]
62
64
  drawables: AndroidDrawable[]
65
+ layoutIds: AndroidLayoutIds
63
66
  }
64
67
 
65
68
  // a color resource's ARGB in the day and the night configuration
@@ -244,6 +247,7 @@ function kotlinPluginVersion(
244
247
  }
245
248
 
246
249
  async function checkModuleGradle(
250
+ projectDir: string,
247
251
  gradle: { file: string; text: string },
248
252
  pins: AndroidPins,
249
253
  catalog: KotlinVersionCatalog,
@@ -258,7 +262,7 @@ async function checkModuleGradle(
258
262
  throw new Error(`${where}: ${value} is not a literal or catalog dependency`)
259
263
  }
260
264
  const platforms = new Map<string, string>()
261
- const dependencies: { coordinate: string; where: string }[] = []
265
+ const dependencies: { coordinate: string; where: string; compile: boolean }[] = []
262
266
  let usesSerialization = false
263
267
  for (const match of gradle.text.matchAll(
264
268
  /\bid\(\s*"([^"]+)"\s*\)|\bkotlin\(\s*"([^"]+)"\s*\)|\balias\(\s*libs\.plugins\.([\w.]+)\s*\)/g,
@@ -291,6 +295,7 @@ async function checkModuleGradle(
291
295
  dependencies.push({
292
296
  coordinate: `${dependency.group}:${dependency.name}:${dependency.version}`,
293
297
  where: gradle.file,
298
+ compile: true,
294
299
  })
295
300
  }
296
301
  } else
@@ -321,6 +326,10 @@ async function checkModuleGradle(
321
326
  if (!call) throw new Error(`${where}: ${text} is not a literal dependency`)
322
327
  const [, configuration = '', value = ''] = call
323
328
  if (/^(test|androidTest|debug|release)/.test(configuration)) continue
329
+ const compile =
330
+ configuration === 'implementation' ||
331
+ configuration === 'api' ||
332
+ configuration === 'compileOnly'
324
333
  const platform = /^platform\(\s*(.*?)\s*\)$/.exec(
325
334
  platformVariables.get(value) ?? value,
326
335
  )
@@ -338,11 +347,13 @@ async function checkModuleGradle(
338
347
  }
339
348
  } else {
340
349
  for (const coordinate of coordinates(value, where))
341
- dependencies.push({ coordinate, where })
350
+ dependencies.push({ coordinate, where, compile })
342
351
  }
343
352
  }
344
353
  }
345
- for (const { coordinate, where } of dependencies) {
354
+ const librarySources: string[] = []
355
+ const seen = new Set<string>()
356
+ for (const { coordinate, where, compile } of dependencies) {
346
357
  const parts = /^([^:]+):([^:]+)(?::([^:]+))?$/.exec(coordinate)
347
358
  if (!parts)
348
359
  throw new Error(`${where}: ${coordinate} is not a group:artifact[:version]`)
@@ -361,10 +372,22 @@ async function checkModuleGradle(
361
372
  throw new Error(
362
373
  `${where}: ${name}:${version} differs from Peach's ${pin} pin ${pins[pin]}`,
363
374
  )
364
- } else if (!PLATFORM_GROUPS.has(group) && !PLATFORM_ARTIFACTS.has(name))
365
- throw new Error(`${where}: ${name} is not in Peach's Kotlin closure`)
375
+ } else if (!PLATFORM_GROUPS.has(group) && !PLATFORM_ARTIFACTS.has(name)) {
376
+ const sources = compile
377
+ ? await mavenKotlinSources(projectDir, { group, artifact, version }, where)
378
+ : null
379
+ if (!sources)
380
+ throw new Error(
381
+ `${where}: ${group}:${artifact}:${version} is not in Peach's Kotlin closure`,
382
+ )
383
+ for (const file of sources) {
384
+ if (seen.has(file)) continue
385
+ seen.add(file)
386
+ librarySources.push(file)
387
+ }
388
+ }
366
389
  }
367
- return { usesSerialization }
390
+ return { usesSerialization, librarySources }
368
391
  }
369
392
 
370
393
  // Resources.selectDefaultTheme: the theme a context without one gets for its
@@ -566,7 +589,8 @@ export async function readKotlinApp(
566
589
  targetSdk: model.targetSdk,
567
590
  }
568
591
  })()
569
- const { usesSerialization } = await checkModuleGradle(
592
+ const { usesSerialization, librarySources } = await checkModuleGradle(
593
+ projectDir,
570
594
  gradle,
571
595
  options.pins,
572
596
  catalog,
@@ -729,6 +753,7 @@ export async function readKotlinApp(
729
753
  }
730
754
  walk(dir)
731
755
  }
756
+ sources.push(...librarySources)
732
757
  if (sources.length === 0)
733
758
  throw new Error(`${moduleDir}/src/main holds no Kotlin sources`)
734
759
  const resources = {
@@ -757,5 +782,6 @@ export async function readKotlinApp(
757
782
  strings: readAndroidStrings(moduleDir),
758
783
  colors: readAppColors(moduleDir, resources),
759
784
  drawables: readAndroidDrawables(moduleDir, resources),
785
+ layoutIds: readAndroidLayoutIds(moduleDir),
760
786
  }
761
787
  }
@@ -18,6 +18,7 @@ import {
18
18
  type BridgeModule,
19
19
  type BridgeSelection,
20
20
  } from './android-bridge-packages'
21
+ import { layoutViewClasses } from './android-view-classes'
21
22
  import { rnxHomeDir } from './home-paths'
22
23
  import { isRecord } from './is-record'
23
24
  import {
@@ -39,6 +40,7 @@ import {
39
40
  type ViewModelClass,
40
41
  } from './kotlin-view-models'
41
42
  import type { DayNight, DrawableFile } from './android-drawables'
43
+ import type { XmlElement } from './android-xml'
42
44
  import type { ExpoKotlinModules } from './expo-kotlin-host'
43
45
  import type { KotlinAppProject, KotlinWindowTheme } from './kotlin-app'
44
46
  import type { KotlinSdkProfile, KotlinToolchain } from './kotlinSdkContract'
@@ -968,7 +970,7 @@ function appResourceSources(app: KotlinAppProject): Record<string, string> {
968
970
  .map(([locale, value]) => `${literal(locale)} to ${render(value)}`)
969
971
  .join(', ')})`,
970
972
  }))
971
- const kinds = [
973
+ const tableKinds = [
972
974
  {
973
975
  type: 'string',
974
976
  table: 'peachAppStrings',
@@ -1007,6 +1009,16 @@ function appResourceSources(app: KotlinAppProject): Record<string, string> {
1007
1009
  .join(',\n')}\n)`,
1008
1010
  })),
1009
1011
  },
1012
+ // a layout is its element tree; one only in a qualified directory has none
1013
+ {
1014
+ type: 'layout',
1015
+ table: 'peachAppLayouts',
1016
+ value: 'LayoutNode?',
1017
+ entries: app.layoutIds.layouts.map((name) => {
1018
+ const element = app.layoutIds.elements.get(name)
1019
+ return { name, init: element ? layoutInit(element) : 'null' }
1020
+ }),
1021
+ },
1010
1022
  // the day and the night ARGB
1011
1023
  {
1012
1024
  type: 'color',
@@ -1017,19 +1029,38 @@ function appResourceSources(app: KotlinAppProject): Record<string, string> {
1017
1029
  init: `intArrayOf(${argb(color.day)}, ${argb(color.night)})`,
1018
1030
  })),
1019
1031
  },
1020
- ].map((kind, index) => ({
1032
+ ]
1033
+ // ids are named in R and in the name table; they hold no Resources value
1034
+ const nameOnly = [{ type: 'id', names: app.layoutIds.ids }]
1035
+ const numbered = <Kind extends { entries: Array<{ name: string }> }>(
1036
+ kind: Kind,
1037
+ index: number,
1038
+ ): Omit<Kind, 'entries'> & {
1039
+ entries: Array<Kind['entries'][number] & { id: string }>
1040
+ } => ({
1021
1041
  ...kind,
1022
1042
  entries: kind.entries.map((entry, number) => ({
1023
1043
  ...entry,
1024
1044
  id: id(index + 1, number),
1025
1045
  })),
1026
- }))
1046
+ })
1047
+ const kinds = tableKinds.map((kind, index) => numbered(kind, index))
1048
+ const viewClasses = layoutViewClasses(app.layoutIds.elements.values())
1049
+ const rKinds = [
1050
+ ...kinds,
1051
+ ...nameOnly.map((kind, index) =>
1052
+ numbered(
1053
+ { type: kind.type, entries: kind.names.map((name) => ({ name })) },
1054
+ kinds.length + index,
1055
+ ),
1056
+ ),
1057
+ ]
1027
1058
  return {
1028
1059
  'R.kt': [
1029
1060
  `package ${app.namespace}`,
1030
1061
  '',
1031
1062
  'object R {',
1032
- ...kinds.flatMap((kind) => [
1063
+ ...rKinds.flatMap((kind) => [
1033
1064
  ` object ${kind.type} {`,
1034
1065
  ...kind.entries.map(
1035
1066
  (entry) => ` const val ${field(entry.name)}: Int = ${entry.id}`,
@@ -1051,6 +1082,10 @@ function appResourceSources(app: KotlinAppProject): Record<string, string> {
1051
1082
  'androidx.compose.ui.graphics.vector.ImageVector',
1052
1083
  'androidx.compose.ui.graphics.vector.addPathNodes',
1053
1084
  'androidx.compose.ui.unit.Dp',
1085
+ 'android.content.Context',
1086
+ 'android.util.AttributeSet',
1087
+ 'android.view.LayoutNode',
1088
+ 'android.view.View',
1054
1089
  ].map((name) => `import ${name}`),
1055
1090
  '',
1056
1091
  ...kinds.flatMap((kind) => [
@@ -1059,16 +1094,45 @@ function appResourceSources(app: KotlinAppProject): Record<string, string> {
1059
1094
  ')',
1060
1095
  '',
1061
1096
  ]),
1097
+ 'internal val peachAppViews: Map<String, (Context, AttributeSet?) -> View> = mapOf(',
1098
+ ...viewClasses.map(
1099
+ (name) =>
1100
+ ` ${literal(name)} to { context, attrs -> ${name}(context, attrs) },`,
1101
+ ),
1102
+ ')',
1103
+ '',
1104
+ `internal val peachAppNames: Map<Int, String> = mapOf(`,
1105
+ ...rKinds.flatMap((kind) =>
1106
+ kind.entries.map(
1107
+ (entry) =>
1108
+ ` ${entry.id} to ${literal(`${app.packageName}:${kind.type}/${entry.name}`)},`,
1109
+ ),
1110
+ ),
1111
+ ')',
1112
+ '',
1062
1113
  'internal val compiledAppResources = PeachAppResources(',
1063
1114
  ...kinds.map(
1064
1115
  (kind) =>
1065
1116
  ` ${kind.table.slice('peachApp'.length).toLowerCase()} = ${kind.table},`,
1066
1117
  ),
1118
+ ' views = peachAppViews,',
1119
+ ' names = peachAppNames,',
1067
1120
  ')',
1068
1121
  ].join('\n'),
1069
1122
  }
1070
1123
  }
1071
1124
 
1125
+ // the elements a layout inflates, without the namespace declarations and
1126
+ // tools attributes aapt2 strips
1127
+ function layoutInit(element: XmlElement): string {
1128
+ const attributes = Object.entries(element.attributes)
1129
+ .filter(([key]) => !key.startsWith('xmlns') && !key.startsWith('tools:'))
1130
+ .map(([key, value]) => `${literal(key)} to ${literal(value)}`)
1131
+ return `LayoutNode(${literal(element.name)}, mapOf(${attributes.join(', ')}), listOf(${element.children
1132
+ .map(layoutInit)
1133
+ .join(', ')}))`
1134
+ }
1135
+
1072
1136
  // a drawable file's table entry: a vector as the ImageVector.Builder calls
1073
1137
  // XmlVectorParser makes, its colors chosen by the configuration's night
1074
1138
  // mode; a bitmap as its encoded bytes
@@ -1171,11 +1235,19 @@ async function compileKotlin(
1171
1235
  const sdk = await loadKotlinSdk(moduleRoot, toolchain)
1172
1236
  const plugin = path.join(sdk.root, 'plugin.jar')
1173
1237
  const sdkLibrary = path.join(sdk.root, `${sdkProfile}.klib`)
1174
- const java = javaSpecs.length
1238
+ const javaRead = javaSpecs.length
1175
1239
  ? await readJavaModuleSpecs(javaSpecs, sdk.root, await ensureToolchain(toolchain))
1176
1240
  : null
1241
+ const java =
1242
+ javaRead && javaRead.specs.some((spec) => !spec.application)
1243
+ ? { ...javaRead, specs: javaRead.specs.filter((spec) => !spec.application) }
1244
+ : null
1245
+ const applicationJava = javaRead?.specs.filter((spec) => spec.application) ?? []
1177
1246
  const generated = {
1178
1247
  ...sourceGenerated,
1248
+ ...Object.fromEntries(
1249
+ applicationJava.map((spec, index) => [`JavaApplication${index}.kt`, spec.source]),
1250
+ ),
1179
1251
  'PeachBindingsInitialization.kt': [
1180
1252
  '@file:OptIn(kotlin.js.ExperimentalJsExport::class)',
1181
1253
  'package dev.peach.jni',
@@ -1209,7 +1281,7 @@ async function compileKotlin(
1209
1281
  sdk: sdk.sha256,
1210
1282
  sdkProfile,
1211
1283
  usesSerialization,
1212
- java: java?.sha256,
1284
+ java: javaRead?.sha256,
1213
1285
  }),
1214
1286
  )
1215
1287
  for (const { file, bytes } of inputs)
@@ -1442,6 +1514,11 @@ async function compileKotlin(
1442
1514
  : ''
1443
1515
  for (const [name, source] of Object.entries(origins))
1444
1516
  output = output.replaceAll(path.join(generatedDir, name), source)
1517
+ for (const [index, spec] of applicationJava.entries())
1518
+ output = output.replaceAll(
1519
+ path.join(generatedDir, `JavaApplication${index}.kt`),
1520
+ spec.file,
1521
+ )
1445
1522
  for (const { file, target } of compiledSources)
1446
1523
  output = output
1447
1524
  .replaceAll(target, file)