peachsim 0.1.935 → 0.1.937

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 (80) hide show
  1. package/dist-lib/agent-daemon-client.cjs +1 -1
  2. package/dist-lib/agent-events.cjs +1 -1
  3. package/dist-lib/agent-identity.cjs +1 -1
  4. package/dist-lib/agent-sessions.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 +48 -11
  16. package/dist-lib/capture-contract.cjs +1 -1
  17. package/dist-lib/capture-contract.mjs +1 -1
  18. package/dist-lib/cli-constants.cjs +1 -1
  19. package/dist-lib/cloud-contract.cjs +1 -1
  20. package/dist-lib/cloud-contract.mjs +1 -1
  21. package/dist-lib/cloud-test-args.cjs +1 -1
  22. package/dist-lib/cloud-test-args.mjs +1 -1
  23. package/dist-lib/cloud.cjs +1 -1
  24. package/dist-lib/cloud.mjs +1 -1
  25. package/dist-lib/config.cjs +1 -1
  26. package/dist-lib/detox/index.cjs +1 -1
  27. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  28. package/dist-lib/expo-swift-bundle.cjs +48 -11
  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 +48 -11
  33. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  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 +5 -4
  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 +1 -1
  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 +5 -4
  49. package/dist-lib/native-app-identity.cjs +40 -3
  50. package/dist-lib/native-app-identity.mjs +36 -2
  51. package/dist-lib/native-firebase-metadata.cjs +1 -1
  52. package/dist-lib/native-firebase-metadata.mjs +1 -1
  53. package/dist-lib/plist.cjs +1 -1
  54. package/dist-lib/plist.mjs +1 -1
  55. package/dist-lib/profiles.cjs +1 -1
  56. package/dist-lib/public-brand.cjs +1 -1
  57. package/dist-lib/react-native-host-modules.cjs +1 -1
  58. package/dist-lib/react-native-host-modules.mjs +1 -1
  59. package/dist-lib/render-mode.cjs +1 -1
  60. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  61. package/dist-lib/sdk.cjs +1 -1
  62. package/dist-lib/sdk.mjs +1 -1
  63. package/dist-lib/skills.cjs +48 -11
  64. package/dist-lib/swift-compile.cjs +1 -1
  65. package/dist-lib/swift-compile.mjs +1 -1
  66. package/dist-lib/swift-package.cjs +1 -1
  67. package/dist-lib/swift-package.mjs +1 -1
  68. package/dist-lib/swift-submit.cjs +1 -1
  69. package/dist-lib/swift-submit.mjs +1 -1
  70. package/dist-lib/swift-toolchain.cjs +1 -1
  71. package/dist-lib/swift.cjs +1 -1
  72. package/dist-lib/vite-swift.cjs +48 -11
  73. package/dist-lib/vite.cjs +1 -1
  74. package/dist-lib/worklet-executable.cjs +1 -1
  75. package/dist-lib/worklet-executable.mjs +1 -1
  76. package/package.json +1 -1
  77. package/src/native-app-identity.ts +47 -5
  78. package/src/swift-host.ts +45 -7
  79. package/src/swift-standins.ts +18 -1
  80. package/src/xcode-project.ts +8 -5
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.935 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.937 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -6036,7 +6036,13 @@ function peachModuleDirs(toolchain, imports) {
6036
6036
  ],
6037
6037
  moduleRoot
6038
6038
  );
6039
- return [import_node_path12.default.join(moduleRoot, ".build", TRIPLE, "release", "Modules")];
6039
+ const release = import_node_path12.default.join(moduleRoot, ".build", TRIPLE, "release");
6040
+ return [
6041
+ import_node_path12.default.join(release, "Modules"),
6042
+ ...import_node_fs12.default.readdirSync(release).filter(
6043
+ (name2) => name2.endsWith(".build") && import_node_fs12.default.existsSync(import_node_path12.default.join(release, name2, "module.modulemap"))
6044
+ ).sort().map((name2) => import_node_path12.default.join(release, name2))
6045
+ ];
6040
6046
  }
6041
6047
  function swiftInterfaceForWasm(text3) {
6042
6048
  return text3.replace(
@@ -6394,6 +6400,8 @@ function standinGenerationInputs(binaries, toolchain) {
6394
6400
  }
6395
6401
  function binaryStandins(binary, toolchain, provided) {
6396
6402
  if (provided.has(binary.target)) return [];
6403
+ if (!binary.url && !import_node_fs12.default.existsSync(binary.dir))
6404
+ return `${import_node_path12.default.basename(binary.dir)} is not on disk; build or fetch it, then run again`;
6397
6405
  let all;
6398
6406
  let digest;
6399
6407
  try {
@@ -7567,7 +7575,7 @@ function readXcodeAppTarget(input, targetName) {
7567
7575
  )
7568
7576
  ];
7569
7577
  const linkedProducts = [];
7570
- const binaryFrameworks = /* @__PURE__ */ new Set();
7578
+ const binaryFrameworks = /* @__PURE__ */ new Map();
7571
7579
  for (const { project, target } of order) {
7572
7580
  const productDependencies = [
7573
7581
  ...list(target.packageProductDependencies),
@@ -7604,7 +7612,8 @@ function readXcodeAppTarget(input, targetName) {
7604
7612
  if (file?.isa !== "PBXFileReference" || !name2) continue;
7605
7613
  if (tree === "BUILT_PRODUCTS_DIR" || tree === "SDKROOT" || tree === "DEVELOPER_DIR")
7606
7614
  continue;
7607
- binaryFrameworks.add(name2);
7615
+ const resolved = fileId ? project.resolvePath(fileId) : null;
7616
+ if (resolved && !binaryFrameworks.has(name2)) binaryFrameworks.set(name2, resolved);
7608
7617
  }
7609
7618
  }
7610
7619
  }
@@ -7636,7 +7645,7 @@ function readXcodeAppTarget(input, targetName) {
7636
7645
  localPackageDirs: [
7637
7646
  ...new Set(localPackageDirs.map((dir) => import_node_fs13.default.realpathSync(dir)))
7638
7647
  ].sort(),
7639
- binaryFrameworks: [...binaryFrameworks].sort(),
7648
+ binaryFrameworks: [...binaryFrameworks].map(([name2, file]) => ({ name: name2, path: file })).sort((left, right) => left.name.localeCompare(right.name)),
7640
7649
  packageResolved: import_node_fs13.default.existsSync(resolvedFile) ? resolvedFile : null,
7641
7650
  appTargets: appTargetNames
7642
7651
  };
@@ -7727,11 +7736,25 @@ function swiftPackages(entry, app) {
7727
7736
  localDirs: app.localPackageDirs,
7728
7737
  provided: entry.provided
7729
7738
  }),
7730
- standins: []
7739
+ standins: [],
7740
+ projectGaps: /* @__PURE__ */ new Map()
7731
7741
  };
7732
7742
  entry.packages = cached2;
7733
7743
  }
7734
- const reached = cached2.packages.binaries.filter((binary) => binary.reached);
7744
+ const vendored = app.binaryFrameworks.map(
7745
+ ({ name: name2, path: dir }) => ({
7746
+ target: name2,
7747
+ source: "the project",
7748
+ url: null,
7749
+ checksum: null,
7750
+ dir,
7751
+ reached: true
7752
+ })
7753
+ );
7754
+ const reached = [
7755
+ ...cached2.packages.binaries.filter((binary) => binary.reached),
7756
+ ...vendored
7757
+ ];
7735
7758
  if (entry.moduleRoot && entry.provided && reached.length > 0) {
7736
7759
  const resolved = swiftModuleToolchain(entry.moduleRoot);
7737
7760
  const toolchain = {
@@ -7754,6 +7777,12 @@ function swiftPackages(entry, app) {
7754
7777
  binaryGaps: built2.gaps
7755
7778
  });
7756
7779
  cached2.standins = built2.standins;
7780
+ cached2.projectGaps = new Map(
7781
+ vendored.flatMap(({ target }) => {
7782
+ const gap = built2.gaps.get(target);
7783
+ return gap ? [[target, gap]] : [];
7784
+ })
7785
+ );
7757
7786
  cached2.standinKey = generation;
7758
7787
  }
7759
7788
  }
@@ -7794,7 +7823,7 @@ function xcodeLayout(root) {
7794
7823
  mainActorIsolation: framework.mainActorIsolation
7795
7824
  });
7796
7825
  }
7797
- const { packages, standins } = swiftPackages(registered, app);
7826
+ const { packages, standins, projectGaps } = swiftPackages(registered, app);
7798
7827
  for (const standin of standins)
7799
7828
  targets.push({
7800
7829
  name: standin.module,
@@ -7829,6 +7858,7 @@ function xcodeLayout(root) {
7829
7858
  targets,
7830
7859
  packages,
7831
7860
  standins,
7861
+ projectGaps,
7832
7862
  targetDirs,
7833
7863
  files: [...files2].sort()
7834
7864
  };
@@ -8234,9 +8264,16 @@ var IOS_SDK_FRAMEWORKS = new Set(ios_sdk_frameworks_default.frameworks);
8234
8264
  async function missingModulesMessage(packagePath, missing) {
8235
8265
  const layout = xcodeLayout(packagePath);
8236
8266
  const gaps = new Map([
8237
- ...(layout?.app.binaryFrameworks ?? []).map(
8238
- (name2) => [name2, { source: "the project", reason: "a vendored binary framework" }]
8239
- ),
8267
+ ...(layout?.app.binaryFrameworks ?? []).map(({ name: name2 }) => {
8268
+ const gap = layout?.projectGaps.get(name2);
8269
+ return [
8270
+ name2,
8271
+ {
8272
+ source: "the project",
8273
+ reason: `a vendored binary framework${gap ? `; no stand-in: ${gap}` : ""}`
8274
+ }
8275
+ ];
8276
+ }),
8240
8277
  ...[...missing.keys()].flatMap(
8241
8278
  (name2) => UNPROVIDED_PLATFORM_MODULES[name2] ? [
8242
8279
  [
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.935 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.937 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.935 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.937 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.935 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.937 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/worklet-executable.ts
4
4
  import { parse } from "acorn";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peachsim",
3
- "version": "0.1.935",
3
+ "version": "0.1.937",
4
4
  "description": "Vite and Metro plugins, testing drivers, and SDK exports for peach.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -3,11 +3,9 @@
3
3
  // against the Xcode Release build settings, or `android/app/build.gradle`), so
4
4
  // a simulator tenant reports the same identity the native Release build does.
5
5
  //
6
- // the values travel in a footer a bundle builder appends to the artifact — the
7
- // same transport shape the Metro module identity footer already uses — and the
8
- // tenant boot reads it from the bundle text before the guest evaluates, then
9
- // stores it on the tenant realm for the native app-information seams. a bundle
10
- // with no native identity footer leaves every seam exactly where it was.
6
+ // the values travel in a bundle footer or a response header bound to unchanged
7
+ // artifact bytes. tenant boot validates both before evaluation and clears the
8
+ // realm store when neither carries identity.
11
9
 
12
10
  export type NativeAppIdentityPlatform = 'ios' | 'android'
13
11
 
@@ -32,6 +30,7 @@ export type NativeAppIdentityFiles = Record<string, string>
32
30
  const NATIVE_APP_IDENTITY_PREFIX = 'globalThis.__sootsimNativeAppIdentity='
33
31
  const METRO_MODULE_PATHS_PREFIX = 'globalThis.__sootsimModulePaths='
34
32
  const APPLIED_NATIVE_APP_IDENTITY_KEY = '__sootsim_native_app_identity'
33
+ export const NATIVE_APP_IDENTITY_HEADER = 'Peach-Native-App-Identity'
35
34
 
36
35
  interface NativeAppIdentityGlobals {
37
36
  __sootsim_native_app_identity?: NativeAppIdentity | null
@@ -99,6 +98,49 @@ export function readNativeAppIdentity(value: unknown): NativeAppIdentity | null
99
98
  }
100
99
  }
101
100
 
101
+ // response metadata carries binary configuration without changing guest bytes.
102
+ export function createNativeAppIdentityHeader(identity: NativeAppIdentity): string {
103
+ return encodeURIComponent(JSON.stringify(readNativeAppIdentity(identity)))
104
+ }
105
+
106
+ function equalIdentityValues(left: unknown, right: unknown): boolean {
107
+ if (left === right) return true
108
+ if (Array.isArray(left) && Array.isArray(right)) {
109
+ return (
110
+ left.length === right.length &&
111
+ left.every((value, index) => equalIdentityValues(value, right[index]))
112
+ )
113
+ }
114
+ if (!isRecord(left) || !isRecord(right)) return false
115
+ const keys = Object.keys(left)
116
+ return (
117
+ keys.length === Object.keys(right).length &&
118
+ keys.every(
119
+ (key) => Object.hasOwn(right, key) && equalIdentityValues(left[key], right[key]),
120
+ )
121
+ )
122
+ }
123
+
124
+ export function readNativeAppIdentityFromBundle(
125
+ source: string,
126
+ header: string | null,
127
+ ): NativeAppIdentity | null {
128
+ const footer = readNativeAppIdentityFooter(source)
129
+ if (header === null) return readNativeAppIdentity(footer)
130
+ let value: unknown
131
+ try {
132
+ value = JSON.parse(decodeURIComponent(header))
133
+ } catch {
134
+ throw new Error('native app identity response header is malformed')
135
+ }
136
+ const identity = readNativeAppIdentity(value)
137
+ if (!identity) throw new Error('native app identity response header is malformed')
138
+ if (footer && !equalIdentityValues(identity, readNativeAppIdentity(footer))) {
139
+ throw new Error('native app identity response header conflicts with bundle footer')
140
+ }
141
+ return identity
142
+ }
143
+
102
144
  // the one realm store every seam reads. set by the tenant boot before the
103
145
  // guest evaluates, cleared on a boot without an identity so a previous app's
104
146
  // identity can never leak into the next load.
package/src/swift-host.ts CHANGED
@@ -29,6 +29,7 @@ import { swiftImplicitResourcePaths, type SwiftPackageTarget } from './swift-pac
29
29
  import {
30
30
  REMOTE_CHECKOUTS,
31
31
  resolveSwiftPackages,
32
+ type SwiftPackageBinary,
32
33
  type SwiftPackageGap,
33
34
  type SwiftPackages,
34
35
  } from './swift-packages'
@@ -202,6 +203,8 @@ interface XcodeProjectEntry {
202
203
  standinKey: string
203
204
  packages: SwiftPackages
204
205
  standins: SwiftStandin[]
206
+ /** why a framework the project vendors got no stand-in, by name. */
207
+ projectGaps: Map<string, string>
205
208
  }
206
209
  }
207
210
 
@@ -233,7 +236,11 @@ export function openXcodeProject(
233
236
  function swiftPackages(
234
237
  entry: XcodeProjectEntry,
235
238
  app: XcodeAppTarget,
236
- ): { packages: SwiftPackages; standins: SwiftStandin[] } {
239
+ ): {
240
+ packages: SwiftPackages
241
+ standins: SwiftStandin[]
242
+ projectGaps: Map<string, string>
243
+ } {
237
244
  const stamp = (file: string | null) =>
238
245
  file && fs.existsSync(file) ? fs.statSync(file).mtimeMs : 0
239
246
  const key = JSON.stringify([
@@ -261,6 +268,7 @@ function swiftPackages(
261
268
  provided: entry.provided,
262
269
  }),
263
270
  standins: [],
271
+ projectGaps: new Map(),
264
272
  }
265
273
  entry.packages = cached
266
274
  }
@@ -269,7 +277,22 @@ function swiftPackages(
269
277
  // their reason with why no stand-in was built. surfaces peach already
270
278
  // ships are skipped up front, so no generated target overrides a shipped
271
279
  // module.
272
- const reached = cached.packages.binaries.filter((binary) => binary.reached)
280
+ // a framework the project vendors links straight into its target, so it
281
+ // stands in the same way a package's binary target does.
282
+ const vendored = app.binaryFrameworks.map(
283
+ ({ name, path: dir }): SwiftPackageBinary => ({
284
+ target: name,
285
+ source: 'the project',
286
+ url: null,
287
+ checksum: null,
288
+ dir,
289
+ reached: true,
290
+ }),
291
+ )
292
+ const reached = [
293
+ ...cached.packages.binaries.filter((binary) => binary.reached),
294
+ ...vendored,
295
+ ]
273
296
  if (entry.moduleRoot && entry.provided && reached.length > 0) {
274
297
  const resolved = swiftModuleToolchain(entry.moduleRoot)
275
298
  const toolchain = {
@@ -292,6 +315,12 @@ function swiftPackages(
292
315
  binaryGaps: built.gaps,
293
316
  })
294
317
  cached.standins = built.standins
318
+ cached.projectGaps = new Map(
319
+ vendored.flatMap(({ target }) => {
320
+ const gap = built.gaps.get(target)
321
+ return gap ? [[target, gap] as const] : []
322
+ }),
323
+ )
295
324
  cached.standinKey = generation
296
325
  }
297
326
  }
@@ -328,6 +357,8 @@ interface XcodeLayout {
328
357
  targets: (SwiftPackageTarget & { exclude: string[] })[]
329
358
  packages: SwiftPackages
330
359
  standins: SwiftStandin[]
360
+ /** why a framework the project vendors got no stand-in, by name. */
361
+ projectGaps: Map<string, string>
331
362
  /** the input directory each app and framework source compiles under. */
332
363
  targetDirs: Map<string, string>
333
364
  files: string[]
@@ -358,7 +389,7 @@ function xcodeLayout(root: string): XcodeLayout | null {
358
389
  mainActorIsolation: framework.mainActorIsolation,
359
390
  })
360
391
  }
361
- const { packages, standins } = swiftPackages(registered, app)
392
+ const { packages, standins, projectGaps } = swiftPackages(registered, app)
362
393
  for (const standin of standins)
363
394
  targets.push({
364
395
  name: standin.module,
@@ -401,6 +432,7 @@ function xcodeLayout(root: string): XcodeLayout | null {
401
432
  targets,
402
433
  packages,
403
434
  standins,
435
+ projectGaps,
404
436
  targetDirs,
405
437
  files: [...files].sort(),
406
438
  }
@@ -1052,10 +1084,16 @@ async function missingModulesMessage(
1052
1084
  ): Promise<string> {
1053
1085
  const layout = xcodeLayout(packagePath)
1054
1086
  const gaps = new Map<string, SwiftPackageGap>([
1055
- ...(layout?.app.binaryFrameworks ?? []).map(
1056
- (name) =>
1057
- [name, { source: 'the project', reason: 'a vendored binary framework' }] as const,
1058
- ),
1087
+ ...(layout?.app.binaryFrameworks ?? []).map(({ name }) => {
1088
+ const gap = layout?.projectGaps.get(name)
1089
+ return [
1090
+ name,
1091
+ {
1092
+ source: 'the project',
1093
+ reason: `a vendored binary framework${gap ? `; no stand-in: ${gap}` : ''}`,
1094
+ },
1095
+ ] as const
1096
+ }),
1059
1097
  ...[...missing.keys()].flatMap((name) =>
1060
1098
  UNPROVIDED_PLATFORM_MODULES[name]
1061
1099
  ? [
@@ -294,7 +294,21 @@ function peachModuleDirs(toolchain: Toolchain, imports: string[]): string[] {
294
294
  ],
295
295
  moduleRoot,
296
296
  )
297
- return [path.join(moduleRoot, '.build', TRIPLE, 'release', 'Modules')]
297
+ // a c target's module map sits in its own build directory, where swiftpm
298
+ // generated it; a peach module re-exporting one fails to load without it
299
+ const release = path.join(moduleRoot, '.build', TRIPLE, 'release')
300
+ return [
301
+ path.join(release, 'Modules'),
302
+ ...fs
303
+ .readdirSync(release)
304
+ .filter(
305
+ (name) =>
306
+ name.endsWith('.build') &&
307
+ fs.existsSync(path.join(release, name, 'module.modulemap')),
308
+ )
309
+ .sort()
310
+ .map((name) => path.join(release, name)),
311
+ ]
298
312
  }
299
313
 
300
314
  // an iOS interface read for wasm: its own flags target wasm, and objective-c
@@ -735,6 +749,9 @@ function binaryStandins(
735
749
  provided: ReadonlySet<string>,
736
750
  ): SwiftStandin[] | string {
737
751
  if (provided.has(binary.target)) return []
752
+ // a local artifact a build step writes is absent from a fresh checkout
753
+ if (!binary.url && !fs.existsSync(binary.dir))
754
+ return `${path.basename(binary.dir)} is not on disk; build or fetch it, then run again`
738
755
  let all: Surface[]
739
756
  let digest: string
740
757
  try {
@@ -154,8 +154,8 @@ export interface XcodeAppTarget extends XcodeModuleTarget {
154
154
  linkedProducts: SwiftLinkedProduct[]
155
155
  /** local package directories the workspace or a project references. */
156
156
  localPackageDirs: string[]
157
- /** prebuilt frameworks and xcframeworks the projects vendor and link, by name. */
158
- binaryFrameworks: string[]
157
+ /** prebuilt frameworks and xcframeworks the projects vendor and link. */
158
+ binaryFrameworks: { name: string; path: string }[]
159
159
  /** the Package.resolved pinning the remote packages' revisions, when resolved. */
160
160
  packageResolved: string | null
161
161
  /** every application target in the workspace, for a chooser. */
@@ -911,7 +911,7 @@ export function readXcodeAppTarget(input: string, targetName?: string): XcodeApp
911
911
  ),
912
912
  ]
913
913
  const linkedProducts: SwiftLinkedProduct[] = []
914
- const binaryFrameworks = new Set<string>()
914
+ const binaryFrameworks = new Map<string, string>()
915
915
  for (const { project, target } of order) {
916
916
  const productDependencies = [
917
917
  ...list(target.packageProductDependencies),
@@ -963,7 +963,8 @@ export function readXcodeAppTarget(input: string, targetName?: string): XcodeApp
963
963
  tree === 'DEVELOPER_DIR'
964
964
  )
965
965
  continue
966
- binaryFrameworks.add(name)
966
+ const resolved = fileId ? project.resolvePath(fileId) : null
967
+ if (resolved && !binaryFrameworks.has(name)) binaryFrameworks.set(name, resolved)
967
968
  }
968
969
  }
969
970
  }
@@ -999,7 +1000,9 @@ export function readXcodeAppTarget(input: string, targetName?: string): XcodeApp
999
1000
  localPackageDirs: [
1000
1001
  ...new Set(localPackageDirs.map((dir) => fs.realpathSync(dir))),
1001
1002
  ].sort(),
1002
- binaryFrameworks: [...binaryFrameworks].sort(),
1003
+ binaryFrameworks: [...binaryFrameworks]
1004
+ .map(([name, file]) => ({ name, path: file }))
1005
+ .sort((left, right) => left.name.localeCompare(right.name)),
1003
1006
  packageResolved: fs.existsSync(resolvedFile) ? resolvedFile : null,
1004
1007
  appTargets: appTargetNames,
1005
1008
  }