peachsim 0.1.2179 → 0.1.2180

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 (95) hide show
  1. package/cli/commands/package.ts +7 -2
  2. package/dist-lib/agent-daemon-client.cjs +1 -1
  3. package/dist-lib/agent-events.cjs +1 -1
  4. package/dist-lib/attached-projects.cjs +1 -1
  5. package/dist-lib/auth/shared-session.cjs +1 -1
  6. package/dist-lib/backend-origin.cjs +1 -1
  7. package/dist-lib/beta.cjs +1 -1
  8. package/dist-lib/beta.mjs +1 -1
  9. package/dist-lib/bridge-constants.cjs +1 -1
  10. package/dist-lib/bridge-contract-input.cjs +1 -1
  11. package/dist-lib/bridge-contract-input.mjs +1 -1
  12. package/dist-lib/bridge-contract.cjs +1 -1
  13. package/dist-lib/bridge-contract.mjs +1 -1
  14. package/dist-lib/bundle-channel.cjs +89 -20
  15. package/dist-lib/capture-contract.cjs +1 -1
  16. package/dist-lib/capture-contract.mjs +1 -1
  17. package/dist-lib/cloud-contract.cjs +1 -1
  18. package/dist-lib/cloud-contract.mjs +1 -1
  19. package/dist-lib/cloud-test-args.cjs +1 -1
  20. package/dist-lib/cloud-test-args.mjs +1 -1
  21. package/dist-lib/cloud.cjs +1 -1
  22. package/dist-lib/cloud.mjs +1 -1
  23. package/dist-lib/config.cjs +1 -1
  24. package/dist-lib/config.mjs +1 -1
  25. package/dist-lib/detox/index.cjs +2 -2
  26. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  27. package/dist-lib/expo-swift-bundle.cjs +76 -16
  28. package/dist-lib/expo-swift-metadata.cjs +1 -1
  29. package/dist-lib/expo-swift-metadata.mjs +1 -1
  30. package/dist-lib/home-paths.cjs +1 -1
  31. package/dist-lib/host/bridge-host.cjs +100 -28
  32. package/dist-lib/host/fetch-proxy-handler.cjs +4 -4
  33. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  34. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  35. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  36. package/dist-lib/host/websocket-proxy.cjs +1 -1
  37. package/dist-lib/index.cjs +4 -4
  38. package/dist-lib/jump-to-source-babel.cjs +1 -1
  39. package/dist-lib/jump-to-source-native.cjs +1 -1
  40. package/dist-lib/kotlin-host.cjs +11 -3
  41. package/dist-lib/menu.cjs +1 -1
  42. package/dist-lib/menu.mjs +1 -1
  43. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  44. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  45. package/dist-lib/metro-production-bundle.cjs +1 -1
  46. package/dist-lib/metro-production-bundle.mjs +1 -1
  47. package/dist-lib/metro.cjs +4 -4
  48. package/dist-lib/native-app-identity-project.cjs +2 -2
  49. package/dist-lib/native-app-identity-project.mjs +2 -2
  50. package/dist-lib/native-app-identity.cjs +1 -1
  51. package/dist-lib/native-app-identity.mjs +1 -1
  52. package/dist-lib/native-firebase-metadata.cjs +1 -1
  53. package/dist-lib/native-firebase-metadata.mjs +1 -1
  54. package/dist-lib/pdfium-raster.cjs +1 -1
  55. package/dist-lib/pdfium-raster.mjs +1 -1
  56. package/dist-lib/plist.cjs +1 -1
  57. package/dist-lib/plist.mjs +1 -1
  58. package/dist-lib/profiles.cjs +1 -1
  59. package/dist-lib/public-brand.cjs +1 -1
  60. package/dist-lib/public-origins-lib-esm.json +24 -24
  61. package/dist-lib/public-origins-lib.json +53 -53
  62. package/dist-lib/react-native-host-modules.cjs +1 -1
  63. package/dist-lib/react-native-host-modules.mjs +1 -1
  64. package/dist-lib/reap-browser.cjs +1 -1
  65. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  66. package/dist-lib/sdk.cjs +1 -1
  67. package/dist-lib/sdk.mjs +1 -1
  68. package/dist-lib/skills.cjs +113 -114
  69. package/dist-lib/swift-compile.cjs +74 -14
  70. package/dist-lib/swift-compile.mjs +74 -14
  71. package/dist-lib/swift-package.cjs +1 -1
  72. package/dist-lib/swift-package.mjs +1 -1
  73. package/dist-lib/swift-submit.cjs +1 -1
  74. package/dist-lib/swift-submit.mjs +1 -1
  75. package/dist-lib/swift-toolchain.cjs +1 -1
  76. package/dist-lib/swift.cjs +1 -1
  77. package/dist-lib/vite-swift.cjs +75 -15
  78. package/dist-lib/vite.cjs +4 -4
  79. package/dist-lib/worklet-executable.cjs +1 -1
  80. package/dist-lib/worklet-executable.mjs +1 -1
  81. package/dist-types/packages/peach-engine/src/cli-companion.d.ts +1 -0
  82. package/dist-types/packages/peach-engine/src/ios/frame-art.d.ts +41 -0
  83. package/dist-types/packages/peach-engine/src/ios/frame-paint.d.ts +43 -0
  84. package/dist-types/packages/peach-engine/src/ios/frame-svg.d.ts +4 -0
  85. package/kotlin/sdk.tar.gz +0 -0
  86. package/package.json +1 -1
  87. package/runtime-companion.json +1 -1
  88. package/sdk/ios/System/Library/Frameworks/UIKit.framework/Headers/UIScrollView.h +8 -25
  89. package/sdk/ios/System/Library/Frameworks/UIKitTypes.framework/Headers/UIKitTypes.h +26 -0
  90. package/src/expo-widgets-bundle.ts +12 -8
  91. package/src/kotlin-host.ts +10 -2
  92. package/src/one-sdk-bundle.ts +1 -0
  93. package/src/screenshots/frame-compose.ts +14 -109
  94. package/src/swift-compile.ts +102 -12
  95. package/src/wasm-optimize.ts +2 -3
@@ -2363,6 +2363,32 @@ typedef NS_ENUM(NSInteger, UIImageRenderingMode) {
2363
2363
  UIImageRenderingModeAlwaysOriginal, // Always draw the original image, without treating it as a template
2364
2364
  UIImageRenderingModeAlwaysTemplate, // Always draw the image as a template image, ignoring its color information
2365
2365
  } API_AVAILABLE(ios(7.0), watchos(2.0));
2366
+
2367
+ typedef NS_ENUM(NSInteger, UIScrollViewIndicatorStyle) {
2368
+ UIScrollViewIndicatorStyleDefault, // Automatically switches appearance based on the user interface style
2369
+ UIScrollViewIndicatorStyleBlack, // Dark indicator, for use on light backgrounds
2370
+ UIScrollViewIndicatorStyleWhite // Light indicator, for use on dark backgrounds
2371
+ } API_UNAVAILABLE(watchos);
2372
+
2373
+ typedef NS_ENUM(NSInteger, UIScrollViewKeyboardDismissMode) {
2374
+ UIScrollViewKeyboardDismissModeNone,
2375
+ UIScrollViewKeyboardDismissModeOnDrag, // dismisses the keyboard when a drag begins
2376
+ UIScrollViewKeyboardDismissModeInteractive, // the keyboard follows the dragging touch off screen, and may be pulled upward again to cancel the dismiss
2377
+ UIScrollViewKeyboardDismissModeOnDragWithAccessory, // dismisses both keyboard and accessory in the style of UIScrollViewKeyboardDismissModeOnDrag
2378
+ UIScrollViewKeyboardDismissModeInteractiveWithAccessory // dismisses both keyboard and accessory in the style of UIScrollViewKeyboardDismissModeInteractive
2379
+ } API_AVAILABLE(ios(7.0)) API_UNAVAILABLE(visionos, watchos);
2380
+
2381
+ typedef NS_ENUM(NSInteger, UIScrollViewIndexDisplayMode) {
2382
+ UIScrollViewIndexDisplayModeAutomatic, // the index will show or hide automatically as needed
2383
+ UIScrollViewIndexDisplayModeAlwaysHidden, // the index will never be displayed
2384
+ } API_AVAILABLE(tvos(10.2)) API_UNAVAILABLE(watchos);
2385
+
2386
+ typedef NS_ENUM(NSInteger, UIScrollViewContentInsetAdjustmentBehavior) {
2387
+ UIScrollViewContentInsetAdjustmentAutomatic, // Similar to .scrollableAxes, but for backward compatibility will also adjust the top & bottom contentInset when the scroll view is owned by a view controller with automaticallyAdjustsScrollViewInsets = YES inside a navigation controller, regardless of whether the scroll view is scrollable
2388
+ UIScrollViewContentInsetAdjustmentScrollableAxes, // Edges for scrollable axes are adjusted (i.e., contentSize.width/height > frame.size.width/height or alwaysBounceHorizontal/Vertical = YES)
2389
+ UIScrollViewContentInsetAdjustmentNever, // contentInset is not adjusted
2390
+ UIScrollViewContentInsetAdjustmentAlways, // contentInset is always adjusted by the scroll view's safeAreaInsets
2391
+ } API_AVAILABLE(ios(11.0), tvos(11.0)) API_UNAVAILABLE(watchos);
2366
2392
  PEACH_SWIFT_CLASS(UIKitCore) @interface UIColor : NSObject <NSSecureCoding, NSCopying>
2367
2393
  @end
2368
2394
 
@@ -5,6 +5,7 @@ import path from 'node:path'
5
5
  import { promisify } from 'node:util'
6
6
  import { stageExpoWidgetAssets } from './expo-widget-assets'
7
7
  import { compiledModuleRequestHandler } from './nitro-bundle'
8
+ import { withSwiftRootLock } from './swift-root-lock'
8
9
  import type { ExpoWidgetExtension } from '../../peach-engine/src/render-worker/expo-widgetContract'
9
10
 
10
11
  export const EXPO_WIDGETS_ROUTE = '/__peach/expo-widgets'
@@ -29,18 +30,21 @@ export async function buildExpoWidgetsBundle(
29
30
  // output path. 58 reads an optional platform and output and otherwise writes
30
31
  // the same package bundle, so one root argument covers both.
31
32
  const packageDir = path.dirname(manifest)
32
- await promisify(execFile)(
33
- process.env.NODE_BINARY || 'node',
34
- [path.join(packageDir, 'scripts/build-bundle.mjs'), appRoot],
35
- { cwd: appRoot, maxBuffer: 16 * 1024 * 1024 },
33
+ const runtimeBundle = await withSwiftRootLock(
34
+ path.join(await fs.realpath(packageDir), 'bundle'),
35
+ async () => {
36
+ await promisify(execFile)(
37
+ process.env.NODE_BINARY || 'node',
38
+ [path.join(packageDir, 'scripts/build-bundle.mjs'), appRoot],
39
+ { cwd: appRoot, maxBuffer: 16 * 1024 * 1024 },
40
+ )
41
+ return fs.readFile(path.join(packageDir, 'bundle/build/ExpoWidgets.bundle'), 'utf8')
42
+ },
36
43
  )
37
44
  const catalog = await stageExpoWidgetAssets(appRoot, origin)
38
45
  return {
39
46
  metadata: {
40
- runtimeBundle: await fs.readFile(
41
- path.join(packageDir, 'bundle/build/ExpoWidgets.bundle'),
42
- 'utf8',
43
- ),
47
+ runtimeBundle,
44
48
  images: catalog?.images ?? {},
45
49
  },
46
50
  }
@@ -131,7 +131,11 @@ function entrySource(ids: string[]): string {
131
131
  '',
132
132
  '@OptIn(kotlin.js.ExperimentalJsExport::class)',
133
133
  '@JsExport',
134
- 'fun peachSetAndroidBuildInfo(model: String, release: String, apiLevel: Int) = Build.setDevice(model, release, apiLevel)',
134
+ 'fun peachSetAndroidDeviceInfo(model: String, release: String, apiLevel: Int, locale: String, timeZone: String) {',
135
+ ' Build.setDevice(model, release, apiLevel)',
136
+ ' java.util.Locale.setDefault(java.util.Locale.forLanguageTag(locale))',
137
+ ' java.util.TimeZone.setDevice(timeZone)',
138
+ '}',
135
139
  '',
136
140
  '@OptIn(kotlin.js.ExperimentalJsExport::class, kotlinx.coroutines.DelicateCoroutinesApi::class)',
137
141
  '@JsExport',
@@ -726,8 +730,12 @@ export async function expoKotlinArtifact(
726
730
  ' ))',
727
731
  '}',
728
732
  '',
729
- '@JsExport fun peachSetAndroidBuildInfo(model: String, release: String, apiLevel: Int) =',
733
+ '@JsExport fun peachSetAndroidDeviceInfo(model: String, release: String, apiLevel: Int, locale: String, timeZone: String) {',
730
734
  ' android.os.Build.setDevice(model, release, apiLevel)',
735
+ ' java.util.Locale.setDefault(java.util.Locale.forLanguageTag(locale))',
736
+ ' java.util.TimeZone.setDevice(timeZone)',
737
+ ' PeachExpoRuntime.configureProcessLocale(locale)',
738
+ '}',
731
739
  '',
732
740
  '@JsExport fun peachExpoListen(notify: () -> Unit) {',
733
741
  ' registered',
@@ -92,6 +92,7 @@ let package = Package(name: "OneSDKGuest", targets: [
92
92
  )
93
93
  const project = projectFor(packagePath)
94
94
  project.oneModule = false
95
+ project.optimize = true
95
96
  const artifact = await artifactFor(project, 'Sources/App/App.swift', moduleRoot)
96
97
  const metadata: OneSDKCompilation = {
97
98
  url: `${origin}/__peach/swift/${artifact.hash}.wasm`,
@@ -2,9 +2,8 @@ import { launchProvisionedPlaywright } from '../../cli/drivers/playwright-provis
2
2
  import { getCliRuntime } from '../cli-runtime.ts'
3
3
  import { cornerRadii, type CornerRadii, type DeviceModel } from '../device-catalog.ts'
4
4
  import type { HousingLayer } from '#peach-cli-contract/ios/duo-housing'
5
+ import type { FramePaintSpec } from '#peach-cli-contract/ios/frame-paint'
5
6
 
6
- const METALLIC_RING_SHADOW =
7
- 'inset 0 0 0 0.5px #000, inset 0 0 0 2px #757575, inset 0 0 0 5px #212121'
8
7
  const FRAME_OUTLINE_BLEED = 1
9
8
 
10
9
  type FrameButtonLayout = {
@@ -40,9 +39,7 @@ export interface FramedScreenshotLayout {
40
39
  logicalFrameRadius: CornerRadii
41
40
  // folding body art, painted before the screen; null draws one black body
42
41
  housingLayers: HousingLayer[] | null
43
- frameBackground: string
44
42
  frameOutline: string
45
- metallicRingShadow: string | null
46
43
  buttons: FrameButtonLayout[]
47
44
  legacyHomeButton: LegacyHomeButtonLayout | null
48
45
  showHomeIndicator: boolean
@@ -149,17 +146,11 @@ export function getFramedScreenshotLayout(model: DeviceModel): FramedScreenshotL
149
146
  housingLayers,
150
147
  // the housing paints its own body layer, shadow included: its frame box is
151
148
  // wider than the body, because the hinge barrel protrudes past the spine.
152
- frameBackground: isLegacyHardware
153
- ? 'linear-gradient(180deg, #1b1c20 0%, #0f1012 42%, #050608 100%)'
154
- : housingLayers
155
- ? 'transparent'
156
- : '#000000',
157
149
  frameOutline: isLegacyHardware
158
150
  ? '0 0 0 1px #1f2125'
159
151
  : housingLayers
160
152
  ? 'none'
161
153
  : '0 0 0 1px #333',
162
- metallicRingShadow: isLegacyHardware || housingLayers ? null : METALLIC_RING_SHADOW,
163
154
  buttons,
164
155
  legacyHomeButton: isLegacyHardware
165
156
  ? {
@@ -182,69 +173,22 @@ export function getFramedScreenshotLayout(model: DeviceModel): FramedScreenshotL
182
173
  }
183
174
  }
184
175
 
185
- function renderSideButtonHtml(button: FrameButtonLayout): string {
186
- const isRight = button.side === 'right'
187
- const isBottom = button.side === 'bottom'
188
- const horizontal = button.side === 'top' || isBottom
189
- const clip = horizontal
190
- ? isBottom
191
- ? 'inset(0 -3px -3px -3px)'
192
- : 'inset(-3px -3px 0 -3px)'
193
- : isRight
194
- ? 'inset(-3px -3px -3px 0)'
195
- : 'inset(-3px 0 -3px -3px)'
196
- return `
197
- <div
198
- aria-hidden="true"
199
- style="
200
- position:absolute;
201
- ${
202
- horizontal
203
- ? `left:${button.top}px;${isBottom ? `bottom:-${button.width}px;` : `top:-${button.width}px;`}`
204
- : `${isRight ? `right:-${button.width}px;` : `left:-${button.width}px;`}top:${button.top}px;`
205
- }
206
- width:${horizontal ? button.height : button.width}px;
207
- height:${horizontal ? button.width : button.height}px;
208
- background-color:#212121;
209
- border-top-left-radius:${isRight || isBottom ? 0 : 1.5}px;
210
- border-bottom-left-radius:${isRight || button.side === 'top' ? 0 : 1.5}px;
211
- border-top-right-radius:${button.side === 'left' || isBottom ? 0 : 1.5}px;
212
- border-bottom-right-radius:${button.side === 'left' || button.side === 'top' ? 0 : 1.5}px;
213
- box-shadow:
214
- inset 0 0.5px 0 #616161,
215
- inset 0 -0.5px 0 #3F3F3F,
216
- ${isRight ? 'inset -0.5px 0 0 #3F3F3F,' : 'inset 0.5px 0 0 #3F3F3F,'}
217
- 0 0 0 0.5px #676767,
218
- 0 0 0 1.5px #000;
219
- clip-path:${clip};
220
- pointer-events:none;
221
- z-index:2;
222
- "
223
- ></div>
224
- `
225
- }
226
-
227
176
  function renderFrameHtml(
228
177
  layout: FramedScreenshotLayout,
229
178
  rawImageDataUrl: string,
230
179
  ): string {
231
- const buttonsHtml = layout.buttons.map(renderSideButtonHtml).join('')
232
-
233
- const ringHtml = layout.metallicRingShadow
234
- ? `
235
- <div
236
- aria-hidden="true"
237
- style="
238
- position:absolute;
239
- inset:0;
240
- border-radius:${getCliRuntime().settings_devices.formatCornerRadii(layout.logicalFrameRadius)};
241
- box-shadow:${layout.metallicRingShadow};
242
- pointer-events:none;
243
- z-index:10;
244
- "
245
- ></div>
246
- `
247
- : ''
180
+ const painter = getCliRuntime().ios_frame_svg
181
+ const paintSpec = {
182
+ width: layout.logicalFrameWidth,
183
+ height: layout.logicalFrameHeight,
184
+ radii: layout.logicalFrameRadius,
185
+ body: layout.legacyHomeButton ? 'legacy' : layout.housingLayers ? null : 'solid',
186
+ ring: layout.legacyHomeButton || layout.housingLayers ? null : painter.METALLIC_RING,
187
+ buttons: layout.buttons
188
+ .filter((button) => button.height > 0)
189
+ .map((button) => ({ ...button, hover: 0 })),
190
+ home: layout.legacyHomeButton,
191
+ } satisfies FramePaintSpec
248
192
 
249
193
  // the folding body's rail, glass ring, corner art, and hinge barrel. no
250
194
  // z-index, so the screen div after it paints on top.
@@ -252,42 +196,6 @@ function renderFrameHtml(
252
196
  .map(getCliRuntime().ios_duo_housing.housingLayerHtml)
253
197
  .join('')
254
198
 
255
- const legacyHomeButtonHtml = layout.legacyHomeButton
256
- ? `
257
- <div
258
- aria-hidden="true"
259
- style="
260
- position:absolute;
261
- top:${layout.legacyHomeButton.top}px;
262
- left:${layout.legacyHomeButton.left}px;
263
- width:${layout.legacyHomeButton.size}px;
264
- height:${layout.legacyHomeButton.size}px;
265
- border-radius:50%;
266
- background:
267
- radial-gradient(circle at 35% 32%, rgba(62,64,68,0.35) 0%, rgba(17,18,20,0.98) 72%, rgba(8,9,11,1) 100%);
268
- box-shadow:
269
- inset 0 0 0 1px rgba(255,255,255,0.06),
270
- inset 0 0 0 ${layout.legacyHomeButton.ring}px rgba(184,192,204,0.45),
271
- 0 0 0 1px rgba(0,0,0,0.5);
272
- z-index:3;
273
- pointer-events:none;
274
- display:flex;
275
- align-items:center;
276
- justify-content:center;
277
- "
278
- >
279
- <div
280
- style="
281
- width:16px;
282
- height:16px;
283
- border-radius:4px;
284
- box-shadow:inset 0 0 0 1.5px rgba(188,194,202,0.5);
285
- "
286
- ></div>
287
- </div>
288
- `
289
- : ''
290
-
291
199
  const homeIndicatorHtml = layout.showHomeIndicator
292
200
  ? `
293
201
  <div
@@ -374,14 +282,11 @@ function renderFrameHtml(
374
282
  border-radius:${layout.housingLayers ? '0' : getCliRuntime().settings_devices.formatCornerRadii(layout.logicalFrameRadius)};
375
283
  box-sizing:border-box;
376
284
  overflow:visible;
377
- background:${layout.frameBackground};
378
285
  box-shadow:${layout.frameOutline};
379
286
  "
380
287
  >
381
- ${buttonsHtml}
382
- ${ringHtml}
288
+ ${painter.frameSvg(paintSpec)}
383
289
  ${housingHtml}
384
- ${legacyHomeButtonHtml}
385
290
  <div
386
291
  id="screen"
387
292
  style="
@@ -1791,6 +1791,32 @@ export function emitSwiftPackageManifest(args: {
1791
1791
  const linkedObjects = Boolean(distribution || prebuilt)
1792
1792
  const cxxFlagsFor = (flags: readonly string[]): readonly string[] =>
1793
1793
  args.deferDarwinOverlay ? withoutWasmOverlayIsystem(flags) : flags
1794
+ // swiftpm writes the module map of a target with no swift sources, so an
1795
+ // installed pod's map of that module (a prebuilt React Native core target
1796
+ // the app's pods also list) would define it twice. a swift target imports
1797
+ // its underlying objective-c module through the pod's map and keeps it.
1798
+ const packageModules = new Set(
1799
+ targets
1800
+ .filter(({ name }) => !args.sourceSwiftTargets?.has(name))
1801
+ .map(({ name }) => swiftTargetModuleName(name)),
1802
+ )
1803
+ const mapsPackageModule = (flag: string): boolean => {
1804
+ if (!flag.startsWith('-fmodule-map-file=')) return false
1805
+ const file = flag
1806
+ .slice('-fmodule-map-file='.length)
1807
+ .replaceAll(SWIFT_NATIVE_INPUT_ROOT, args.root)
1808
+ if (!file.includes('/Pods/Headers/')) return false
1809
+ const map =
1810
+ args.files.get(file) ?? (existsSync(file) ? readFileSync(file) : undefined)
1811
+ const name = map === undefined ? undefined : moduleMapName(map.toString())
1812
+ return name !== undefined && packageModules.has(name)
1813
+ }
1814
+ const withoutPackageModuleMaps = (flags: readonly string[]): string[] =>
1815
+ flags.filter((flag) => !mapsPackageModule(flag))
1816
+ const targetCFlags = (flags: readonly string[]) =>
1817
+ cFlagArray(withoutPackageModuleMaps(flags))
1818
+ const targetCxxFlags = (flags: readonly string[]) =>
1819
+ cxxFlagArray(withoutPackageModuleMaps(flags))
1794
1820
  const reachedFrameworks = new Set<string>()
1795
1821
  const reach = (name: string) => {
1796
1822
  if (
@@ -1908,7 +1934,11 @@ export function emitSwiftPackageManifest(args: {
1908
1934
  // swiftpm expands target settings into one command; large native import
1909
1935
  // closures exceed exec's argument limit. keep their ordered bytes on disk.
1910
1936
  const swiftFlagArray = (literal: string): string => {
1911
- const flags: string[] = JSON.parse(literal.replace(/,\s*]$/, ']'))
1937
+ const flags: string[] = JSON.parse(literal.replace(/,\s*]$/, ']')).filter(
1938
+ (flag: string, index: number, all: string[]) =>
1939
+ !mapsPackageModule(flag) &&
1940
+ !(flag === '-Xcc' && mapsPackageModule(all[index + 1] ?? '')),
1941
+ )
1912
1942
  const content = flags.map((flag) => JSON.stringify(flag)).join('\n') + '\n'
1913
1943
  const digest = createHash('sha256').update(content).digest('hex')
1914
1944
  const file = path.join(args.root, '.peach-swift-arguments', `${digest}.rsp`)
@@ -2182,7 +2212,7 @@ export function emitSwiftPackageManifest(args: {
2182
2212
  .map((entry) => ` ${entry},`)
2183
2213
  .join(
2184
2214
  '',
2185
- )} .unsafeFlags(${flagArray(cFlagArray([...nativeFlags, ...native.clang.flags]))})]`,
2215
+ )} .unsafeFlags(${flagArray(targetCFlags([...nativeFlags, ...native.clang.flags]))})]`,
2186
2216
  ...(nativeCxx
2187
2217
  ? [
2188
2218
  `cxxSettings: [${cxxDefines}${clangSettingEntries(
@@ -2192,13 +2222,13 @@ export function emitSwiftPackageManifest(args: {
2192
2222
  .map((entry) => ` ${entry},`)
2193
2223
  .join(
2194
2224
  '',
2195
- )} .unsafeFlags(${flagArray(cxxFlagArray([...cxxFlagsFor(nativeFlags), ...native.clang.cxxFlags]))})]`,
2225
+ )} .unsafeFlags(${flagArray(targetCxxFlags([...cxxFlagsFor(nativeFlags), ...native.clang.cxxFlags]))})]`,
2196
2226
  ]
2197
2227
  : []),
2198
2228
  ].join(', ')
2199
2229
  : [
2200
- `cSettings: [${cDefines}${declaredClangSettings(target, 'c')} .unsafeFlags(${flagArray(cFlagArray([...nativeFlags, ...(native?.flags ?? [])]))})]`,
2201
- `cxxSettings: [${cxxDefines}${declaredClangSettings(target, 'cxx')} .unsafeFlags(${flagArray(cxxFlagArray([...cxxFlagsFor(nativeFlags), ...(native?.cxxFlags ?? [])]))})]`,
2230
+ `cSettings: [${cDefines}${declaredClangSettings(target, 'c')} .unsafeFlags(${flagArray(targetCFlags([...nativeFlags, ...(native?.flags ?? [])]))})]`,
2231
+ `cxxSettings: [${cxxDefines}${declaredClangSettings(target, 'cxx')} .unsafeFlags(${flagArray(targetCxxFlags([...cxxFlagsFor(nativeFlags), ...(native?.cxxFlags ?? [])]))})]`,
2202
2232
  ].join(', ')
2203
2233
  const exclusions = native?.clang
2204
2234
  ? []
@@ -2287,10 +2317,10 @@ export function emitSwiftPackageManifest(args: {
2287
2317
  ` path: ${JSON.stringify(group.path)},`,
2288
2318
  ` sources: ${JSON.stringify(group.sources)},`,
2289
2319
  ` publicHeadersPath: ${JSON.stringify(native.groups ? 'include' : 'PeachSwiftImplementation')},`,
2290
- ` cSettings: [${macroSettings(owner?.cDefines)}${declaredClangSettings(owner, 'c')} .unsafeFlags(${flagArray(cFlagArray([...nativeFlags, '-I', native.includeDirectory, ...(args.headerDirectory ? ['-I', args.headerDirectory] : []), ...group.flags]))})],`,
2320
+ ` cSettings: [${macroSettings(owner?.cDefines)}${declaredClangSettings(owner, 'c')} .unsafeFlags(${flagArray(targetCFlags([...nativeFlags, '-I', native.includeDirectory, ...(args.headerDirectory ? ['-I', args.headerDirectory] : []), ...group.flags]))})],`,
2291
2321
  ...(group.sources.some((source) => /\.(?:cc|cpp|cxx|mm)$/.test(source))
2292
2322
  ? [
2293
- ` cxxSettings: [${macroSettings(owner?.cxxDefines)}${declaredClangSettings(owner, 'cxx')} .unsafeFlags(${flagArray(cxxFlagArray([...cxxFlagsFor(nativeFlags), '-I', native.includeDirectory, ...group.cxxFlags]))})]),`,
2323
+ ` cxxSettings: [${macroSettings(owner?.cxxDefines)}${declaredClangSettings(owner, 'cxx')} .unsafeFlags(${flagArray(targetCxxFlags([...cxxFlagsFor(nativeFlags), '-I', native.includeDirectory, ...group.cxxFlags]))})]),`,
2294
2324
  ]
2295
2325
  : [' ),']),
2296
2326
  ])
@@ -4300,7 +4330,11 @@ export function writeSwiftProject(
4300
4330
  const target = bySourcePath.find(
4301
4331
  (candidate) => candidate.path === '.' || file.startsWith(`${candidate.path}/`),
4302
4332
  )
4303
- if (target) swiftTargets.add(target.name)
4333
+ // swiftpm never compiles an excluded file (a prebuilt core's Swift)
4334
+ const excluded = target?.exclude?.some(
4335
+ (name) => file === path.posix.join(target.path, name),
4336
+ )
4337
+ if (target && !excluded) swiftTargets.add(target.name)
4304
4338
  }
4305
4339
  // swiftpm builds a clang target's module only for an importer: a swift
4306
4340
  // import, or a framework-style include or @import in any original source.
@@ -5650,6 +5684,9 @@ export function writeSwiftProject(
5650
5684
  frameworkLink,
5651
5685
  [
5652
5686
  ...prebuilt.objects.map(quote),
5687
+ '-Wl,--start-lib',
5688
+ ...prebuilt.lazyObjects.map(quote),
5689
+ '-Wl,--end-lib',
5653
5690
  ...darwinRecordsRegion(prebuilt.providers.map(quote)),
5654
5691
  ].join('\n') + '\n',
5655
5692
  )
@@ -6178,7 +6215,15 @@ function linkedFrameworkTargets(
6178
6215
  )
6179
6216
  const targets = compileTargets(input)
6180
6217
  const planning = targets.length > 0 ? targets : [{ name: 'App', path: '.' }]
6181
- const siblings = swiftTargetDependencies(input.files, planning, libraries.keys())
6218
+ // a target's prebuilt library links the units its package excludes from
6219
+ // compilation (a React Native core), so their imports reach frameworks too
6220
+ const siblings = swiftTargetDependencies(
6221
+ input.files,
6222
+ planning.map((target) =>
6223
+ input.libraries?.[target.name] ? { ...target, exclude: [] } : target,
6224
+ ),
6225
+ libraries.keys(),
6226
+ )
6182
6227
  const reached = new Set<string>()
6183
6228
  const reach = (name: string) => {
6184
6229
  if (name === 'SwiftUI') return
@@ -6409,8 +6454,13 @@ function removeCaseCollidingModuleArtifacts(
6409
6454
 
6410
6455
  /** a source checkout's framework build: what the app link reads from it. */
6411
6456
  interface SwiftFrameworkProducts {
6457
+ /** the distribution-shaped root whose lib/ holds the framework objects */
6458
+ root: string
6412
6459
  modules: string
6460
+ /** objects the link loads whole */
6413
6461
  objects: string[]
6462
+ /** objects the link loads only for a symbol it resolves */
6463
+ lazyObjects: string[]
6414
6464
  owners: Map<string, string>
6415
6465
  /** the Darwin module's providers, linked as an archive ahead of the sdk's */
6416
6466
  providers: string[]
@@ -6572,9 +6622,41 @@ async function buildFrameworkProducts(
6572
6622
  .sort()
6573
6623
  .map((file) => path.join(providerDir, file))
6574
6624
  for (const provider of providers) linked.owners.set(provider, 'DarwinRecords')
6625
+ // the framework objects link as a distribution's do: under lib/ with the names
6626
+ // build-distribution.ts gives them, Objective-C roots whole and the rest as an
6627
+ // archive region the pruner trims (swift-wasm-prune.ts reads <root>/lib).
6628
+ // Darwin stays whole: a distribution leaves it to the sdk's image.
6629
+ const archive = path.join(scratch, 'lib')
6630
+ rmSync(archive, { recursive: true, force: true })
6631
+ mkdirSync(archive)
6632
+ const archived = new Map<string, string>()
6633
+ for (const object of objects) {
6634
+ const target = linked.owners.get(object)
6635
+ const dir = path.join(release, `${target}.build`)
6636
+ if (!target || target === 'Darwin' || !object.startsWith(dir + path.sep)) continue
6637
+ const name = `${target}-${path
6638
+ .relative(dir, object)
6639
+ .replaceAll(path.sep, '-')
6640
+ .replace(/\.swift\.o$/, '.o')}`
6641
+ symlinkSync(object, path.join(archive, name))
6642
+ archived.set(object, `lib/${name}`)
6643
+ }
6644
+ const roots = swiftObjcRootObjects(`${scratch}\n${args.frameworkKey}`, scratch, [
6645
+ ...archived.values(),
6646
+ ])
6647
+ const linkedObjects = objects.map((object) => {
6648
+ const name = archived.get(object)
6649
+ return name
6650
+ ? { path: path.join(scratch, name), lazy: !roots.has(name) }
6651
+ : { path: object, lazy: false }
6652
+ })
6575
6653
  return {
6654
+ root: scratch,
6576
6655
  modules: path.join(release, 'Modules'),
6577
- objects,
6656
+ objects: linkedObjects.filter((object) => !object.lazy).map((object) => object.path),
6657
+ lazyObjects: linkedObjects
6658
+ .filter((object) => object.lazy)
6659
+ .map((object) => object.path),
6578
6660
  owners: linked.owners,
6579
6661
  providers,
6580
6662
  link,
@@ -6788,6 +6870,8 @@ function swiftLinkStamp(args: {
6788
6870
  }
6789
6871
  for (const object of args.prebuilt.objects)
6790
6872
  hash.update(`object\n${object}\n${statOf(object)}`)
6873
+ for (const object of args.prebuilt.lazyObjects)
6874
+ hash.update(`lazy-object\n${object}\n${statOf(object)}`)
6791
6875
  for (const object of args.prebuilt.providers)
6792
6876
  hash.update(`provider\n${object}\n${statOf(object)}`)
6793
6877
  for (const map of args.prebuilt.moduleMaps)
@@ -7302,7 +7386,7 @@ function compileSwiftProjectOnce(
7302
7386
  pruneSwiftWasmLink(
7303
7387
  root,
7304
7388
  options.toolchainBin,
7305
- frameworkRoot,
7389
+ prebuilt?.root ?? frameworkRoot,
7306
7390
  input.staticClangTargets,
7307
7391
  options.project,
7308
7392
  undefined,
@@ -7328,9 +7412,15 @@ function compileSwiftProjectOnce(
7328
7412
  optimized,
7329
7413
  })
7330
7414
  else if (input.optimize) await optimizeWasm(output, optimized)
7415
+ const executable = input.optimize ? optimized : output
7416
+ await runCompiler(
7417
+ path.join(options.toolchainBin, 'llvm-objcopy'),
7418
+ ['--remove-section=.swift1_autolink_entries', executable],
7419
+ buildOptions,
7420
+ )
7331
7421
  const artifact = {
7332
7422
  hash,
7333
- bytes: readFileSync(input.optimize ? optimized : output),
7423
+ bytes: readFileSync(executable),
7334
7424
  icu: await icu,
7335
7425
  }
7336
7426
  if (!options.project) saveCompiledArtifact(root, artifact)
@@ -4,11 +4,10 @@ import { installVerifiedTarball } from '@contrast/runtime-delivery'
4
4
  import { BINARYEN_VERSION, wasmOpt } from 'wasm-opt'
5
5
  import { rnxHomeDir } from './home-paths'
6
6
 
7
- // the linked executable of a Nitro or Turbo module, size-optimized. clang's own
7
+ // packaged native executables, size-optimized. clang's own
8
8
  // optimizer pass is off (-O0 on the link) so an edit never pays for it and the
9
9
  // artifact never depends on the machine; this runs the pinned Binaryen once per
10
- // cached artifact instead. the features are the ones the link declares, and the
11
- // pass drops the custom sections (autolink entries, producers) no host reads.
10
+ // cached artifact instead. the features are the ones the link declares.
12
11
  const FEATURES = [
13
12
  'threads',
14
13
  'bulk-memory',