peachsim 0.1.1620 → 0.1.1635

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 (185) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +45 -6
  3. package/cli/app-config.ts +4 -1
  4. package/cli/app-fonts.ts +39 -18
  5. package/cli/app-stubs.ts +224 -9
  6. package/cli/bridge-flow-runner.ts +248 -156
  7. package/cli/cloud-client.ts +68 -57
  8. package/cli/command-registry.ts +1 -0
  9. package/cli/commands/box/search-command.ts +3 -2
  10. package/cli/commands/box.ts +10 -29
  11. package/cli/commands/control.ts +391 -92
  12. package/cli/commands/doctor.ts +17 -184
  13. package/cli/commands/flow.ts +79 -11
  14. package/cli/commands/inspect/core.ts +10 -5
  15. package/cli/commands/inspect/settling.ts +71 -22
  16. package/cli/commands/inspect/wait-ready.ts +12 -0
  17. package/cli/commands/inspect.ts +26 -3
  18. package/cli/commands/install-desktop.ts +1 -0
  19. package/cli/commands/maestro-compare.ts +105 -67
  20. package/cli/commands/maestro.ts +9 -1
  21. package/cli/commands/platform.ts +71 -36
  22. package/cli/commands/record.ts +21 -1
  23. package/cli/commands/screenshot.ts +23 -1
  24. package/cli/commands/serve.ts +7 -2
  25. package/cli/commands/setup.ts +19 -42
  26. package/cli/commands/swift.ts +5 -0
  27. package/cli/commands/upload.ts +51 -8
  28. package/cli/desktop-companion.ts +102 -17
  29. package/cli/dev-build-freshness.ts +37 -2
  30. package/cli/drivers/desktop.ts +14 -1
  31. package/cli/drivers/playwright-sim-host.ts +44 -8
  32. package/cli/drivers/playwright.ts +9 -8
  33. package/cli/drivers/types.ts +5 -0
  34. package/cli/maestro-accessibility.ts +53 -0
  35. package/cli/maestro-compare.ts +108 -12
  36. package/cli/main.ts +2 -1
  37. package/cli/outbound-endpoints.ts +35 -6
  38. package/cli/parent-pid.ts +0 -9
  39. package/cli/pause-aware-timeout.ts +102 -0
  40. package/cli/remote-simulators.ts +112 -0
  41. package/cli/remoteSimulatorContract.ts +21 -0
  42. package/cli/scene-video.ts +89 -0
  43. package/cli/ws-bridge.ts +23 -14
  44. package/detox/attempt-events.cjs +4 -0
  45. package/detox/expectations.ts +5 -1
  46. package/detox/index.ts +241 -181
  47. package/detox/motion-change.cjs +73 -0
  48. package/detox/motion-change.d.cts +21 -0
  49. package/detox/navigation-lifecycle.ts +22 -4
  50. package/dist-lib/agent-daemon-client.cjs +3 -2
  51. package/dist-lib/agent-events.cjs +1 -1
  52. package/dist-lib/attached-projects.cjs +1 -1
  53. package/dist-lib/auth/shared-session.cjs +3 -2
  54. package/dist-lib/backend-origin.cjs +1 -1
  55. package/dist-lib/beta.cjs +1 -1
  56. package/dist-lib/beta.mjs +1 -1
  57. package/dist-lib/bridge-constants.cjs +13 -1
  58. package/dist-lib/bridge-contract-input.cjs +1 -1
  59. package/dist-lib/bridge-contract-input.mjs +1 -1
  60. package/dist-lib/bridge-contract.cjs +14 -8
  61. package/dist-lib/bridge-contract.mjs +13 -7
  62. package/dist-lib/bundle-channel.cjs +10480 -7962
  63. package/dist-lib/capture-contract.cjs +1 -1
  64. package/dist-lib/capture-contract.mjs +1 -1
  65. package/dist-lib/cloud-contract.cjs +1 -1
  66. package/dist-lib/cloud-contract.mjs +1 -1
  67. package/dist-lib/cloud-test-args.cjs +1 -1
  68. package/dist-lib/cloud-test-args.mjs +1 -1
  69. package/dist-lib/cloud.cjs +3 -2
  70. package/dist-lib/cloud.mjs +3 -2
  71. package/dist-lib/config.cjs +10 -6
  72. package/dist-lib/config.mjs +10 -6
  73. package/dist-lib/detox/index.cjs +1854 -966
  74. package/dist-lib/dev-bundle-resolution.cjs +64 -24
  75. package/dist-lib/expo-swift-bundle.cjs +8715 -6271
  76. package/dist-lib/expo-swift-metadata.cjs +7 -4
  77. package/dist-lib/expo-swift-metadata.mjs +7 -4
  78. package/dist-lib/home-paths.cjs +32 -3
  79. package/dist-lib/host/bridge-host.cjs +10135 -7060
  80. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  81. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  82. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  83. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  84. package/dist-lib/host/websocket-proxy.cjs +1 -1
  85. package/dist-lib/index.cjs +1843 -408
  86. package/dist-lib/jump-to-source-babel.cjs +1 -1
  87. package/dist-lib/jump-to-source-native.cjs +1 -1
  88. package/dist-lib/kotlin-host.cjs +1 -1
  89. package/dist-lib/menu.cjs +3 -2
  90. package/dist-lib/menu.mjs +3 -2
  91. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  92. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  93. package/dist-lib/metro-production-bundle.cjs +5 -4
  94. package/dist-lib/metro-production-bundle.mjs +5 -4
  95. package/dist-lib/metro.cjs +1182 -418
  96. package/dist-lib/native-app-identity-project.cjs +782 -23
  97. package/dist-lib/native-app-identity-project.mjs +781 -23
  98. package/dist-lib/native-app-identity.cjs +276 -9
  99. package/dist-lib/native-app-identity.mjs +273 -8
  100. package/dist-lib/native-firebase-metadata.cjs +5 -4
  101. package/dist-lib/native-firebase-metadata.mjs +5 -4
  102. package/dist-lib/plist.cjs +1 -1
  103. package/dist-lib/plist.mjs +1 -1
  104. package/dist-lib/profiles.cjs +1 -1
  105. package/dist-lib/public-brand.cjs +3 -2
  106. package/dist-lib/react-native-host-modules.cjs +8 -1
  107. package/dist-lib/react-native-host-modules.mjs +8 -1
  108. package/dist-lib/scripts/dev-server-scanner.cjs +106 -28
  109. package/dist-lib/sdk.cjs +115 -34
  110. package/dist-lib/sdk.mjs +115 -33
  111. package/dist-lib/skills.cjs +15274 -9904
  112. package/dist-lib/swift-compile.cjs +1165 -173
  113. package/dist-lib/swift-compile.mjs +1212 -203
  114. package/dist-lib/swift-package.cjs +12 -3
  115. package/dist-lib/swift-package.mjs +12 -3
  116. package/dist-lib/swift-submit.cjs +7 -1
  117. package/dist-lib/swift-submit.mjs +5 -1
  118. package/dist-lib/swift-toolchain.cjs +133 -18
  119. package/dist-lib/swift.cjs +1 -1
  120. package/dist-lib/vite-swift.cjs +2648 -862
  121. package/dist-lib/vite.cjs +70 -14
  122. package/dist-lib/worklet-executable.cjs +25 -2
  123. package/dist-lib/worklet-executable.mjs +25 -2
  124. package/kotlin/AppSources/dev/peach/app/PeachDrawable.kt +21 -14
  125. package/kotlin/Sources/androidx/annotation/DrawableRes.kt +14 -0
  126. package/package.json +2 -2
  127. package/scripts/dev-server-scanner.ts +35 -14
  128. package/skills/peach-perf/SKILL.md +11 -5
  129. package/skills/peach-setup/SKILL.md +16 -19
  130. package/src/admitted-mac-build.ts +34 -8
  131. package/src/android-app-identity.ts +146 -0
  132. package/src/android-autolinking.ts +75 -0
  133. package/src/android-string-value.ts +36 -0
  134. package/src/android-strings.ts +5 -36
  135. package/src/androidAutolinkingContract.ts +40 -0
  136. package/src/bridge-constants.ts +11 -0
  137. package/src/bridge-contract.ts +22 -34
  138. package/src/bundleGlobalFooter.ts +26 -7
  139. package/src/codex-client.ts +3 -2
  140. package/src/config.ts +15 -0
  141. package/src/desktop-host-delivery.ts +5 -1
  142. package/src/dev-bundle-resolution.ts +60 -26
  143. package/src/expo-kotlin-host.ts +6 -36
  144. package/src/expo-manifest-body.ts +13 -0
  145. package/src/expo-swift-bundle.ts +17 -12
  146. package/src/expo-swift-host.ts +640 -192
  147. package/src/expo-swift-ready.ts +78 -0
  148. package/src/expoSwiftContract.ts +19 -0
  149. package/src/home-paths.ts +48 -2
  150. package/src/host/bridge-host.ts +12 -57
  151. package/src/host/staged-host-files.ts +29 -2
  152. package/src/installed-package-versions.ts +54 -14
  153. package/src/launchMetadataContract.ts +24 -0
  154. package/src/metro-plugin.ts +37 -42
  155. package/src/native-app-identity-project.ts +142 -5
  156. package/src/native-app-identity.ts +176 -11
  157. package/src/native-firebase-config.ts +209 -13
  158. package/src/nitro-compile.ts +2 -5
  159. package/src/nitro-source.ts +147 -63
  160. package/src/process-group.ts +114 -61
  161. package/src/process-output.ts +0 -9
  162. package/src/public-brand.ts +1 -0
  163. package/src/react-native-bridge-modules.ts +416 -0
  164. package/src/react-native-host-modules.ts +7 -0
  165. package/src/sim-client.ts +6 -4
  166. package/src/swift-binary-modules.ts +2 -2
  167. package/src/swift-compile.ts +1007 -108
  168. package/src/swift-compiler-digest.ts +14 -0
  169. package/src/swift-distribution.lock.json +40 -12
  170. package/src/swift-distribution.ts +61 -9
  171. package/src/swift-host.ts +149 -56
  172. package/src/swift-icu.ts +14 -6
  173. package/src/swift-objc-sdk.ts +133 -10
  174. package/src/swift-objc-standin.ts +38 -3
  175. package/src/swift-package-manifest.ts +21 -6
  176. package/src/swift-package.ts +15 -1
  177. package/src/swift-packages.ts +119 -60
  178. package/src/swift-standin-emit.ts +57 -9
  179. package/src/swift-standins.ts +185 -25
  180. package/src/swift-submit.ts +10 -0
  181. package/src/swift-toolchain.ts +149 -23
  182. package/src/vite-plugin.ts +20 -20
  183. package/src/worklet-executable.ts +30 -3
  184. package/src/worklets-babel.ts +35 -14
  185. package/src/xcode-project.ts +10 -0
@@ -2,14 +2,16 @@
2
2
  // each linked product resolves through swiftpm's own reading of its package
3
3
  // (swift-package-manifest.ts) to the source targets that build it; remote
4
4
  // packages are fetched at the revision the app's Package.resolved pins, into
5
- // .build/peach/checkouts under the app's root. swift and c-family source
5
+ // PEACH_HOME/cache/swift-packages, isolated by the app root. swift and c-family source
6
6
  // targets use the common compiler backend. objective-c, assembly and binary
7
7
  // targets the host built no stand-in for stay missing with their reason;
8
8
  // the targets beside them still compile.
9
9
 
10
10
  import { execFileSync } from 'node:child_process'
11
+ import { createHash } from 'node:crypto'
11
12
  import fs from 'node:fs'
12
13
  import path from 'node:path'
14
+ import { cacheDir } from './home-paths'
13
15
  import {
14
16
  swiftImplicitResourcePaths,
15
17
  swiftPackageIdentity,
@@ -19,7 +21,7 @@ import {
19
21
  import {
20
22
  swiftPackageManifest,
21
23
  swiftTargetFiles,
22
- type SwiftManifestDefine,
24
+ type SwiftManifestSetting,
23
25
  type SwiftPackageManifest,
24
26
  } from './swift-package-manifest'
25
27
 
@@ -43,7 +45,8 @@ export interface SwiftPackageSourceTarget extends SwiftPackageTarget {
43
45
  packageDir: string
44
46
  remote: boolean
45
47
  /** the conditions its manifest defines, before a build configuration picks them. */
46
- packageDefines: SwiftManifestDefine[]
48
+ packageDefines: SwiftManifestSetting[]
49
+ packageLibraries: SwiftManifestSetting[]
47
50
  }
48
51
 
49
52
  /** why a product or module does not compile, and where it comes from. */
@@ -74,22 +77,29 @@ export interface SwiftPackageBinary {
74
77
  dir: string
75
78
  /** a linked product's closure reaches it, so the app links it. */
76
79
  reached: boolean
80
+ /** a source Objective-C target: public headers relative to dir. */
81
+ publicHeaders?: string
82
+ /** dependency module public headers, relative to dir. */
83
+ headerDependencies?: { module: string; path: string }[]
77
84
  }
78
85
 
79
- /** where checkouts live, relative to the app's root. */
80
- export const REMOTE_CHECKOUTS = path.join('.build', 'peach', 'checkouts')
86
+ /** dependency checkouts belong to peach, isolated by the canonical app root. */
87
+ export function remoteSwiftCheckouts(root: string): string {
88
+ const key = createHash('sha256')
89
+ .update(fs.realpathSync(root))
90
+ .digest('hex')
91
+ .slice(0, 32)
92
+ fs.mkdirSync(cacheDir(), { recursive: true })
93
+ return path.join(fs.realpathSync(cacheDir()), 'swift-packages', key, 'checkouts')
94
+ }
81
95
 
82
96
  // sources requiring an objective-c runtime or assembly lowering.
83
97
  const UNSUPPORTED_NATIVE = /\.(m|mm|s|S)$/
84
98
 
85
99
  const BINARY = 'is a binary framework'
86
- // of the reasons a target or product cannot compile, a binary framework's
87
- // leads: no source exists for it, while another target can retain its
88
- // source-specific reason
89
- const blocker = (reasons: (string | null)[]): string | null =>
90
- reasons.find((reason) => reason?.includes(BINARY)) ??
91
- reasons.find((reason) => reason !== null) ??
92
- null
100
+ // retain every independent blocker in dependency order. a product can reach
101
+ // several unsupported targets, and fixing one must not reveal a hidden next one.
102
+ const blockers = (reasons: readonly string[][]): string[] => [...new Set(reasons.flat())]
93
103
 
94
104
  interface Pin {
95
105
  location: string
@@ -185,7 +195,7 @@ export function resolveSwiftPackages(options: {
185
195
  binaries: [],
186
196
  }
187
197
  const pins = resolved ? readPins(resolved) : null
188
- const checkouts = path.join(rootDir, REMOTE_CHECKOUTS)
198
+ const checkouts = remoteSwiftCheckouts(rootDir)
189
199
 
190
200
  const packages = new Map<string, LoadedPackage | string>()
191
201
  // a package by local directory or remote url, or why it cannot load
@@ -205,8 +215,6 @@ export function resolveSwiftPackages(options: {
205
215
  : 'no Package.resolved pins it; resolve packages in Xcode once'
206
216
  } else {
207
217
  fs.mkdirSync(checkouts, { recursive: true })
208
- // fetched sources never show up as changes in the app's repository
209
- fs.writeFileSync(path.join(path.dirname(checkouts), '.gitignore'), '*\n')
210
218
  loaded = {
211
219
  source: source.url,
212
220
  remote: true,
@@ -219,35 +227,40 @@ export function resolveSwiftPackages(options: {
219
227
  }
220
228
 
221
229
  const targets = new Map<string, SwiftPackageSourceTarget>()
222
- // why each target of each package cannot compile, or null once it can
223
- const targetOutcomes = new Map<string, string | null>()
224
- const resolveTarget = (pkg: LoadedPackage, name: string): string | null => {
230
+ // every reason each target cannot compile; empty once it can
231
+ const targetOutcomes = new Map<string, string[]>()
232
+ const resolveTarget = (pkg: LoadedPackage, name: string): string[] => {
225
233
  const key = `${pkg.manifest.dir}\0${name}`
226
234
  if (targetOutcomes.has(key)) return targetOutcomes.get(key)!
227
235
  // a dependency cycle resolves as compiling; swiftpm rejects real cycles
228
- targetOutcomes.set(key, null)
229
- const outcome = ((): string | null => {
230
- if (provided.has(name)) return null
236
+ targetOutcomes.set(key, [])
237
+ const outcome = ((): string[] => {
238
+ if (provided.has(name)) return []
231
239
  const target = pkg.manifest.targets.get(name)
232
- if (!target) return `${path.basename(pkg.manifest.dir)} declares no target ${name}`
233
- if (target.type === 'binary') {
234
- const gap = options.binaryGaps?.get(name)
235
- return `${name} ${BINARY}${gap ? `; ${gap}` : ''}`
236
- }
237
- if (target.type === 'system') return `${name} is a system C library`
238
- if (target.type !== 'regular') return `${name} is a ${target.type} target`
239
- const files = swiftTargetFiles(pkg.manifest, target)
240
- if (files.some((file) => UNSUPPORTED_NATIVE.test(file)))
241
- return `${name} is an Objective-C or assembly target`
240
+ if (!target)
241
+ return [`${path.basename(pkg.manifest.dir)} declares no target ${name}`]
242
+ const files =
243
+ target.type === 'regular' || target.type === 'system'
244
+ ? swiftTargetFiles(pkg.manifest, target)
245
+ : []
246
+ const gap = options.binaryGaps?.get(name)
247
+ const ownReason =
248
+ target.type === 'binary'
249
+ ? `${name} ${BINARY}${gap ? `; ${gap}` : ''}`
250
+ : target.type !== 'regular' && target.type !== 'system'
251
+ ? `${name} is a ${target.type} target`
252
+ : files.some((file) => UNSUPPORTED_NATIVE.test(file))
253
+ ? `${name} is an Objective-C or assembly target${gap ? `; ${gap}` : ''}`
254
+ : null
242
255
  // every dependency resolves, so a failing one never hides the rest
243
256
  // of the closure from the compile or from the report
244
257
  const reasons = target.dependencies.map((dependency) => {
245
258
  if (dependency.kind === 'target') return resolveTarget(pkg, dependency.name)
246
259
  const nested = resolveDependencyProduct(pkg, dependency.package, dependency.name)
247
- return nested && `${dependency.name}: ${nested}`
260
+ return nested.map((reason) => `${dependency.name}: ${reason}`)
248
261
  })
249
- const blocked = blocker(reasons)
250
- if (blocked) return blocked
262
+ const blocked = blockers([ownReason ? [ownReason] : [], ...reasons])
263
+ if (blocked.length > 0) return blocked
251
264
  const relative = files.map((file) => path.relative(target.dir, file))
252
265
  targets.set(`${pkg.manifest.dir}\0${name}`, {
253
266
  name,
@@ -257,6 +270,7 @@ export function resolveSwiftPackages(options: {
257
270
  packageDir: pkg.manifest.dir,
258
271
  remote: pkg.remote,
259
272
  packageDefines: target.defines,
273
+ packageLibraries: target.libraries,
260
274
  dependencies: target.dependencies.flatMap((dependency) => {
261
275
  if (dependency.kind === 'target') return [dependency]
262
276
  if (provided.has(dependency.name)) return []
@@ -265,32 +279,35 @@ export function resolveSwiftPackages(options: {
265
279
  source &&
266
280
  packages.get('dir' in source ? path.resolve(source.dir) : source.url)
267
281
  return nested && typeof nested !== 'string'
268
- ? (nested.manifest.products.get(dependency.name) ?? []).map((name): SwiftTargetDependency => ({
269
- kind: 'target',
270
- name,
271
- }))
282
+ ? (nested.manifest.products.get(dependency.name) ?? []).map(
283
+ (name): SwiftTargetDependency => ({
284
+ kind: 'target',
285
+ name,
286
+ }),
287
+ )
272
288
  : []
273
289
  }),
274
290
  executable: false,
291
+ ...(target.type === 'system' ? { systemLibrary: true } : {}),
275
292
  languageMode: target.languageMode,
276
293
  mainActorIsolation: target.mainActorIsolation,
277
294
  resources:
278
295
  target.declaresResources || swiftImplicitResourcePaths(relative).length > 0,
279
296
  })
280
- return null
297
+ return []
281
298
  })()
282
299
  targetOutcomes.set(key, outcome)
283
300
  return outcome
284
301
  }
285
302
  // products resolved anywhere in the closure, by package, with why one
286
303
  // cannot compile: a wrapper package's product is often the module name
287
- const productOutcomes = new Map<string, { pkg: LoadedPackage; reason: string | null }>()
288
- const resolveProduct = (pkg: LoadedPackage, product: string): string | null => {
289
- if (provided.has(product)) return null
304
+ const productOutcomes = new Map<string, { pkg: LoadedPackage; reason: string[] }>()
305
+ const resolveProduct = (pkg: LoadedPackage, product: string): string[] => {
306
+ if (provided.has(product)) return []
290
307
  const roots = pkg.manifest.products.get(product)
291
308
  const reason = !roots
292
- ? `${path.basename(pkg.manifest.dir)} has no library ${product}`
293
- : blocker(roots.map((name) => resolveTarget(pkg, name)))
309
+ ? [`${path.basename(pkg.manifest.dir)} has no library ${product}`]
310
+ : blockers(roots.map((name) => resolveTarget(pkg, name)))
294
311
  productOutcomes.set(`${pkg.manifest.dir}\0${product}`, { pkg, reason })
295
312
  return reason
296
313
  }
@@ -299,18 +316,18 @@ export function resolveSwiftPackages(options: {
299
316
  pkg: LoadedPackage,
300
317
  packageName: string,
301
318
  product: string,
302
- ): string | null => {
303
- if (provided.has(product)) return null
319
+ ): string[] => {
320
+ if (provided.has(product)) return []
304
321
  const dependency = pkg.manifest.packages.get(packageName)
305
322
  if (!dependency)
306
- return `${path.basename(pkg.manifest.dir)} names no package ${packageName}`
323
+ return [`${path.basename(pkg.manifest.dir)} names no package ${packageName}`]
307
324
  if ('dir' in dependency) {
308
325
  const loaded = load({ kind: 'local', dir: dependency.dir })
309
- return typeof loaded === 'string' ? loaded : resolveProduct(loaded, product)
326
+ return typeof loaded === 'string' ? [loaded] : resolveProduct(loaded, product)
310
327
  }
311
- if (!options.provided) return null
328
+ if (!options.provided) return []
312
329
  const loaded = load({ kind: 'remote', url: dependency.url })
313
- return typeof loaded === 'string' ? loaded : resolveProduct(loaded, product)
330
+ return typeof loaded === 'string' ? [loaded] : resolveProduct(loaded, product)
314
331
  }
315
332
 
316
333
  const unnamed = () =>
@@ -326,11 +343,11 @@ export function resolveSwiftPackages(options: {
326
343
  : load(source)
327
344
  const reason =
328
345
  pkg === undefined
329
- ? 'no package the workspace references vends it'
346
+ ? ['no package the workspace references vends it']
330
347
  : typeof pkg === 'string'
331
- ? pkg
348
+ ? [pkg]
332
349
  : resolveProduct(pkg, product)
333
- if (reason)
350
+ if (reason.length > 0)
334
351
  result.unavailable.set(product, {
335
352
  source:
336
353
  source.kind === 'remote'
@@ -340,7 +357,7 @@ export function resolveSwiftPackages(options: {
340
357
  : source.kind === 'local'
341
358
  ? source.dir
342
359
  : 'the workspace',
343
- reason,
360
+ reason: reason.join('; '),
344
361
  })
345
362
  }
346
363
 
@@ -352,24 +369,35 @@ export function resolveSwiftPackages(options: {
352
369
  if (target.type === 'test' || target.type === 'plugin' || target.type === 'macro')
353
370
  continue
354
371
  if (target.type === 'executable') continue
355
- if (target.type === 'binary')
372
+ const objc =
373
+ target.type === 'regular' &&
374
+ swiftTargetFiles(pkg.manifest, target).some((file) => /\.(m|mm)$/.test(file))
375
+ if (target.type === 'binary' || objc)
356
376
  result.binaries.push({
357
377
  target: name,
358
378
  source: pkg.source,
359
379
  url: target.url,
360
380
  checksum: target.checksum,
361
- dir: target.dir,
381
+ dir: objc ? pkg.manifest.dir : target.dir,
362
382
  reached: false,
383
+ ...(objc
384
+ ? {
385
+ publicHeaders: path.relative(
386
+ pkg.manifest.dir,
387
+ path.join(target.dir, target.publicHeadersPath),
388
+ ),
389
+ }
390
+ : {}),
363
391
  })
364
392
  const reason = resolveTarget(pkg, name)
365
- if (reason && !result.modules.has(name))
366
- result.modules.set(name, { source: pkg.source, reason })
393
+ if (reason.length > 0 && !result.modules.has(name))
394
+ result.modules.set(name, { source: pkg.source, reason: reason.join('; ') })
367
395
  }
368
396
  }
369
397
  for (const [key, { pkg, reason }] of productOutcomes) {
370
398
  const product = key.slice(key.indexOf('\0') + 1)
371
- if (reason && !result.modules.has(product))
372
- result.modules.set(product, { source: pkg.source, reason })
399
+ if (reason.length > 0 && !result.modules.has(product))
400
+ result.modules.set(product, { source: pkg.source, reason: reason.join('; ') })
373
401
  }
374
402
  // what compiles is every buildable target in a linked product's closure,
375
403
  // decided per target: a product with one target behind a binary still
@@ -410,6 +438,37 @@ export function resolveSwiftPackages(options: {
410
438
  if (pkg && typeof pkg !== 'string')
411
439
  binary.reached = reached.has(`${pkg.manifest.dir}\0${binary.target}`)
412
440
  }
441
+ for (const binary of result.binaries) {
442
+ if (binary.publicHeaders === undefined) continue
443
+ const pkg = packages.get(binary.source)
444
+ if (!pkg || typeof pkg === 'string') continue
445
+ const headers = new Map<string, string>()
446
+ const visited = new Set<string>()
447
+ const visit = (owner: LoadedPackage, name: string) => {
448
+ const key = `${owner.manifest.dir}\0${name}`
449
+ if (visited.has(key)) return
450
+ visited.add(key)
451
+ const target = owner.manifest.targets.get(name)
452
+ if (!target) return
453
+ const dir = path.join(target.dir, target.publicHeadersPath)
454
+ for (const dependency of target.dependencies) {
455
+ if (dependency.kind === 'target') visit(owner, dependency.name)
456
+ else {
457
+ const source = owner.manifest.packages.get(dependency.package)
458
+ const nested =
459
+ source &&
460
+ packages.get('dir' in source ? path.resolve(source.dir) : source.url)
461
+ if (nested && typeof nested !== 'string')
462
+ for (const root of nested.manifest.products.get(dependency.name) ?? [])
463
+ visit(nested, root)
464
+ }
465
+ }
466
+ if (name !== binary.target && fs.existsSync(dir))
467
+ headers.set(name, path.relative(binary.dir, dir))
468
+ }
469
+ visit(pkg, binary.target)
470
+ binary.headerDependencies = [...headers].map(([module, path]) => ({ module, path }))
471
+ }
413
472
  result.targets = [...targets.entries()]
414
473
  .filter(([key]) => reached.has(key))
415
474
  .map(([, target]) => target)
@@ -20,6 +20,7 @@ export type GraphSymbol = {
20
20
  pathComponents: string[]
21
21
  declarationFragments?: SymbolFragment[]
22
22
  accessLevel: string
23
+ functionSignature?: { returns?: SymbolFragment[] }
23
24
  availability?: {
24
25
  domain?: string
25
26
  isUnconditionallyUnavailable?: boolean
@@ -43,6 +44,8 @@ export type StandinOptions = {
43
44
  module: string
44
45
  /** where the declarations came from, for the banner: sdk, version, artifact. */
45
46
  source: string
47
+ /** source Objective-C targets keep declarations with inert native bodies. */
48
+ inert?: boolean
46
49
  /** modules the source imports beside Foundation and UIKit. */
47
50
  imports?: string[]
48
51
  /** precise symbol id → swift literal for enum cases and option values. */
@@ -87,7 +90,7 @@ export type StandinResult = {
87
90
  }
88
91
 
89
92
  /** bump when emitted source changes: the stand-in cache keys on it. */
90
- export const STANDIN_EMIT_VERSION = '6'
93
+ export const STANDIN_EMIT_VERSION = '10'
91
94
 
92
95
  /** the modules every stand-in source imports. */
93
96
  export const STANDIN_IMPORTS = ['Foundation', 'UIKit']
@@ -331,6 +334,47 @@ export function emitSwiftStandin(
331
334
  ])
332
335
  }
333
336
 
337
+ // DEGRADED(swift-objc-runtime): source objc targets retain their api with inert native bodies.
338
+ // return values follow the imported declaration, never the app's call sites.
339
+ const inertValue = (type: string): string => {
340
+ const value = type.trim().replace(/!$/, '?')
341
+ if (value.endsWith('?')) return 'nil'
342
+ if (value === 'Void' || value === '()') return '()'
343
+ if (value === 'Bool') return 'false'
344
+ if (
345
+ /^(?:U?Int(?:8|16|32|64)?|Float|Double|CGFloat|TimeInterval|NSTimeInterval)$/.test(
346
+ value,
347
+ )
348
+ )
349
+ return '0'
350
+ if (value === 'String') return '""'
351
+ if (value.startsWith('[')) return value.includes(':') ? '[:]' : '[]'
352
+ if (value === 'Any' || value === 'AnyObject') return 'NSObject()'
353
+ const typeId = typeByPath.get(value.replace(`${options.module}.`, ''))
354
+ const symbol = typeId && byId.get(typeId)
355
+ if (typeId && symbol && symbol.kind.identifier === 'swift.enum') {
356
+ const first = membersOf(typeId).find(
357
+ (member) => member.kind.identifier === 'swift.enum.case',
358
+ )
359
+ if (first) return `.${leaf(first).replace(/\(.*/, '')}`
360
+ }
361
+ return `${value}()`
362
+ }
363
+ const inertBody = (symbol: GraphSymbol): string => {
364
+ const declaration = text(symbol)
365
+ if (symbol.kind.identifier === 'swift.init') {
366
+ if (/\bconvenience\b/.test(declaration)) return 'self.init()'
367
+ const parent = owner.get(symbol.identifier.precise)
368
+ return parent && superclass.has(parent) ? 'super.init()' : ''
369
+ }
370
+ const type =
371
+ symbol.kind.identifier.includes('property') ||
372
+ symbol.kind.identifier === 'swift.var'
373
+ ? /:\s*(.*?)\s*(?:\{|$)/.exec(declaration)?.[1]
374
+ : symbol.functionSignature?.returns?.map((fragment) => fragment.spelling).join('')
375
+ return type ? `return ${inertValue(type)}` : ''
376
+ }
377
+
334
378
  const lines: string[] = []
335
379
  const owners: string[] = []
336
380
  const omit = options.omit ?? new Map<string, string>()
@@ -426,7 +470,8 @@ export function emitSwiftStandin(
426
470
  : /\bResult</.test(core)
427
471
  ? measured?.resultBody
428
472
  : undefined
429
- const body = overloadBody ?? measured?.body ?? trap(path)
473
+ const body =
474
+ overloadBody ?? measured?.body ?? (options.inert ? inertBody(member) : trap(path))
430
475
  switch (member.kind.identifier) {
431
476
  case 'swift.property':
432
477
  case 'swift.type.property':
@@ -452,7 +497,7 @@ export function emitSwiftStandin(
452
497
  return
453
498
  }
454
499
  emit(
455
- `${head}${computed} { get${effects} { ${body} }${settable && !effects ? ` ${parsed.nonmutating ? 'nonmutating ' : ''}set { ${measured?.set ?? trap(path)} }` : ''} }`,
500
+ `${head}${computed} { get${effects} { ${body} }${settable && !effects ? ` ${parsed.nonmutating ? 'nonmutating ' : ''}set { ${measured?.set ?? (options.inert ? '' : trap(path))} }` : ''} }`,
456
501
  )
457
502
  return
458
503
  }
@@ -646,7 +691,7 @@ export function emitSwiftStandin(
646
691
  externalRoot(id) !== null
647
692
  )
648
693
  emit(
649
- `${inner}public required init?(coder: NSCoder) { ${trap(`${path}.init(coder:)`)} }`,
694
+ `${inner}public required init?(coder: NSCoder) { ${options.inert ? 'return nil' : trap(`${path}.init(coder:)`)} }`,
650
695
  )
651
696
  emit(`${indent}}`)
652
697
  return
@@ -814,7 +859,7 @@ export function emitSwiftStandin(
814
859
  .replace(/^(?:weak |unowned )/, '')
815
860
  .replace(/^((?:static )?)let /, '$1var ')
816
861
  const witness = `${owner}.${leaf(member)}`
817
- const body = trap(witness)
862
+ const body = options.inert ? inertBody(member) : trap(witness)
818
863
  if (
819
864
  member.kind.identifier === 'swift.property' ||
820
865
  member.kind.identifier === 'swift.type.property'
@@ -872,7 +917,7 @@ export function emitSwiftStandin(
872
917
  ? measured?.resultBody
873
918
  : undefined
874
919
  emit(
875
- `${attrs}public ${spelled} { ${overloadBody ?? measured?.body ?? trap(path)} }`,
920
+ `${attrs}public ${spelled} { ${overloadBody ?? measured?.body ?? (options.inert ? inertBody(symbol) : trap(path))} }`,
876
921
  )
877
922
  } else if (kind === 'swift.var') {
878
923
  const parsed = splitAccessors(text(symbol).replace(/^let /, 'var '))
@@ -882,7 +927,9 @@ export function emitSwiftStandin(
882
927
  // as `var` with no setter.
883
928
  const measured = behavior.get(path)
884
929
  if (measured) used.add(path)
885
- emit(`public ${parsed.decl} { ${measured?.body ?? trap(path)} }`)
930
+ emit(
931
+ `public ${parsed.decl} { ${measured?.body ?? (options.inert ? inertBody(symbol) : trap(path))} }`,
932
+ )
886
933
  } else skipped.push({ path, reason: `unhandled top-level ${kind}` })
887
934
  })
888
935
 
@@ -929,8 +976,9 @@ export function emitSwiftStandin(
929
976
  const imports = [...STANDIN_IMPORTS, ...(options.imports ?? [])]
930
977
  const banner = [
931
978
  `// stand-in for the binary ${options.module} module, generated from ${options.source}.`,
932
- `// do not hand-edit: unconfigured bodies trap because the vendor binary cannot run`,
933
- `// in peach; app native stubs may supply configured behavior.`,
979
+ options.inert
980
+ ? '// generated from public headers; native behavior is inert in peach.'
981
+ : '// unconfigured bodies trap because the vendor binary cannot run in peach.',
934
982
  '',
935
983
  ...imports.map((name) => `import ${name}`),
936
984
  '',