peachsim 0.1.2225 → 0.1.2229

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 (118) hide show
  1. package/cli/bridge-flow-runner.ts +56 -3
  2. package/cli/commands/control.ts +27 -1
  3. package/cli/commands/device.ts +1 -1
  4. package/cli/commands/flow.ts +27 -1
  5. package/cli/commands/package.ts +9 -2
  6. package/cli/drivers/playwright.ts +1 -0
  7. package/cli/drivers/types.ts +3 -0
  8. package/dist-lib/agent-daemon-client.cjs +1 -1
  9. package/dist-lib/agent-events.cjs +1 -1
  10. package/dist-lib/attached-projects.cjs +1 -1
  11. package/dist-lib/auth/shared-session.cjs +1 -1
  12. package/dist-lib/backend-origin.cjs +1 -1
  13. package/dist-lib/beta.cjs +1 -1
  14. package/dist-lib/beta.mjs +1 -1
  15. package/dist-lib/bridge-constants.cjs +1 -1
  16. package/dist-lib/bridge-contract-input.cjs +1 -1
  17. package/dist-lib/bridge-contract-input.mjs +1 -1
  18. package/dist-lib/bridge-contract.cjs +1 -1
  19. package/dist-lib/bridge-contract.mjs +1 -1
  20. package/dist-lib/bundle-channel.cjs +108 -13
  21. package/dist-lib/capture-contract.cjs +1 -1
  22. package/dist-lib/capture-contract.mjs +1 -1
  23. package/dist-lib/cloud-contract.cjs +1 -1
  24. package/dist-lib/cloud-contract.mjs +1 -1
  25. package/dist-lib/cloud-test-args.cjs +1 -1
  26. package/dist-lib/cloud-test-args.mjs +1 -1
  27. package/dist-lib/cloud.cjs +1 -1
  28. package/dist-lib/cloud.mjs +1 -1
  29. package/dist-lib/config.cjs +1 -1
  30. package/dist-lib/config.mjs +1 -1
  31. package/dist-lib/detox/index.cjs +5 -2
  32. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  33. package/dist-lib/expo-swift-bundle.cjs +106 -11
  34. package/dist-lib/expo-swift-metadata.cjs +1 -1
  35. package/dist-lib/expo-swift-metadata.mjs +1 -1
  36. package/dist-lib/home-paths.cjs +1 -1
  37. package/dist-lib/host/bridge-host.cjs +108 -13
  38. package/dist-lib/host/fetch-proxy-handler.cjs +5 -5
  39. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  40. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  41. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  42. package/dist-lib/host/websocket-proxy.cjs +1 -1
  43. package/dist-lib/index.cjs +8 -6
  44. package/dist-lib/jump-to-source-babel.cjs +1 -1
  45. package/dist-lib/jump-to-source-native.cjs +1 -1
  46. package/dist-lib/kotlin-host.cjs +1 -1
  47. package/dist-lib/menu.cjs +16 -14
  48. package/dist-lib/menu.mjs +16 -14
  49. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  50. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  51. package/dist-lib/metro-production-bundle.cjs +11 -4
  52. package/dist-lib/metro-production-bundle.mjs +10 -4
  53. package/dist-lib/metro.cjs +8 -6
  54. package/dist-lib/native-app-identity-project.cjs +6 -4
  55. package/dist-lib/native-app-identity-project.mjs +6 -4
  56. package/dist-lib/native-app-identity.cjs +1 -1
  57. package/dist-lib/native-app-identity.mjs +1 -1
  58. package/dist-lib/native-firebase-metadata.cjs +1 -1
  59. package/dist-lib/native-firebase-metadata.mjs +1 -1
  60. package/dist-lib/pdfium-raster.cjs +1 -1
  61. package/dist-lib/pdfium-raster.mjs +1 -1
  62. package/dist-lib/plist.cjs +1 -1
  63. package/dist-lib/plist.mjs +1 -1
  64. package/dist-lib/profiles.cjs +1 -1
  65. package/dist-lib/public-brand.cjs +1 -1
  66. package/dist-lib/public-origins-lib-esm.json +25 -24
  67. package/dist-lib/public-origins-lib.json +56 -53
  68. package/dist-lib/react-native-host-modules.cjs +1 -1
  69. package/dist-lib/react-native-host-modules.mjs +1 -1
  70. package/dist-lib/reap-browser.cjs +1 -1
  71. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  72. package/dist-lib/sdk.cjs +1 -1
  73. package/dist-lib/sdk.mjs +1 -1
  74. package/dist-lib/skills.cjs +183 -27
  75. package/dist-lib/swift-compile.cjs +68 -4
  76. package/dist-lib/swift-compile.mjs +67 -4
  77. package/dist-lib/swift-package.cjs +1 -1
  78. package/dist-lib/swift-package.mjs +1 -1
  79. package/dist-lib/swift-submit.cjs +1 -1
  80. package/dist-lib/swift-submit.mjs +1 -1
  81. package/dist-lib/swift-toolchain.cjs +1 -1
  82. package/dist-lib/swift.cjs +1 -1
  83. package/dist-lib/vite-swift.cjs +105 -10
  84. package/dist-lib/vite.cjs +5 -5
  85. package/dist-lib/worklet-executable.cjs +1 -1
  86. package/dist-lib/worklet-executable.mjs +1 -1
  87. package/dist-types/packages/peach-engine/src/bundle-parse-receipt.d.ts +17 -0
  88. package/dist-types/packages/peach-engine/src/engine/canvaskit-api.d.ts +5 -1
  89. package/dist-types/packages/peach-engine/src/engine/font-config.d.ts +2 -2
  90. package/dist-types/packages/peach-engine/src/engine/native-layout.d.ts +11 -3
  91. package/dist-types/packages/peach-engine/src/engine/native-paragraph.d.ts +4 -0
  92. package/dist-types/packages/peach-engine/src/engine/node.d.ts +2 -0
  93. package/dist-types/packages/peach-engine/src/engine/scene-table.d.ts +1 -1
  94. package/dist-types/packages/peach-engine/src/engine/types.d.ts +9 -1
  95. package/dist-types/packages/peach-engine/src/engine/yoga-layout.d.ts +2 -0
  96. package/dist-types/packages/peach-engine/src/metro-fingerprint.d.ts +3 -2
  97. package/dist-types/packages/peach-engine/src/render-worker/compositor-protocol.d.ts +1 -0
  98. package/dist-types/packages/peach-engine/src/render-worker/protocol.d.ts +3 -3
  99. package/dist-types/packages/peach-engine/src/rolldown-bundle-inventory.d.ts +0 -5
  100. package/dist-types/packages/peach-kit/src/capabilities.d.ts +2 -0
  101. package/dist-types/packages/peach-kit/src/internal.d.ts +1 -1
  102. package/dist-types/packages/peach-kit/src/ios-tint.d.ts +2 -0
  103. package/kotlin/sdk.tar.gz +0 -0
  104. package/package.json +1 -1
  105. package/runtime-companion.json +1 -1
  106. package/sdk/ios/System/Library/Frameworks/UIKit.framework/Headers/UIControl.h +0 -26
  107. package/sdk/ios/System/Library/Frameworks/UIKitTypes.framework/Headers/UIKitTypes.h +27 -0
  108. package/sdk/ios/usr/include/ObjectiveC.apinotes +380 -7
  109. package/src/appPackageContract.ts +1 -1
  110. package/src/menu.ts +17 -99
  111. package/src/menuContract.ts +102 -0
  112. package/src/metro-production-bundle.ts +15 -2
  113. package/src/settingsContract.ts +8 -8
  114. package/src/swift-compile.ts +77 -2
  115. package/src/swift-host.ts +27 -2
  116. package/src/swift-standin-emit.ts +1 -0
  117. package/src/swift-standins.ts +8 -0
  118. package/src/xcode-project.ts +8 -2
package/src/menu.ts CHANGED
@@ -1,107 +1,25 @@
1
+ import {
2
+ type SootSimMenuPlatform,
3
+ type SootSimScaleMode,
4
+ type SootSimAmbientGlowLevel,
5
+ SOOTSIM_AMBIENT_GLOW_LEVELS,
6
+ SOOTSIM_AMBIENT_GLOW_LABELS,
7
+ type SootSimStageBackground,
8
+ SOOTSIM_STAGE_BACKGROUNDS,
9
+ SOOTSIM_STAGE_BACKGROUND_LABELS,
10
+ type SootSimRenderResolution,
11
+ SOOTSIM_RENDER_RESOLUTION_LEVELS,
12
+ SOOTSIM_RENDER_RESOLUTION_LABELS,
13
+ type SootSimScreenEffect,
14
+ SOOTSIM_SCREEN_EFFECTS,
15
+ SOOTSIM_SCREEN_EFFECT_LABELS,
16
+ } from './menuContract'
1
17
  import { rnxPublicBrand } from './public-brand'
2
18
 
3
19
  // public sootsim menu protocol. this file is deliberately pure data and pure
4
20
  // functions so hosts can import it without pulling the engine or canvaskit graph.
5
21
 
6
- export const SOOTSIM_ENGINE_EVENT_NAME = 'sootsim:engine-event'
7
- export const SOOTSIM_SHELL_COMMAND_NAME = 'sootsim:shell-command'
8
-
9
- export type SootSimMenuPlatform = 'browser' | 'electron'
10
- export type SootSimScaleMode = 'physical' | 'point' | 'pixel' | 'fit'
11
-
12
- // ambient glow strength. one setting with three states rather than an on/off
13
- // pair plus a separate strength: every host that carries the preference (the
14
- // menus, the persisted setting, the ?ambientGlow= boot seed, the live
15
- // set-ambient-glow push, electron's shared config) then carries exactly one
16
- // value and cannot end up half-configured.
17
- export type SootSimAmbientGlowLevel = 'off' | 'soft' | 'full'
18
- export const SOOTSIM_AMBIENT_GLOW_LEVELS = ['off', 'soft', 'full'] as const
19
-
20
- export function isSootSimAmbientGlowLevel(
21
- value: unknown,
22
- ): value is SootSimAmbientGlowLevel {
23
- return value === 'off' || value === 'soft' || value === 'full'
24
- }
25
-
26
- export const SOOTSIM_AMBIENT_GLOW_LABELS: Record<SootSimAmbientGlowLevel, string> = {
27
- off: 'Off',
28
- soft: 'Soft',
29
- full: 'Full',
30
- }
31
-
32
- // the stage behind the device: system follows the mac's appearance (white in
33
- // light mode, black in dark), or one pinned color. one value per host, like
34
- // the glow level above.
35
- export type SootSimStageBackground = 'system' | 'light' | 'dark' | 'gray'
36
- export const SOOTSIM_STAGE_BACKGROUNDS = ['system', 'light', 'dark', 'gray'] as const
37
-
38
- export function isSootSimStageBackground(
39
- value: unknown,
40
- ): value is SootSimStageBackground {
41
- return value === 'system' || value === 'light' || value === 'dark' || value === 'gray'
42
- }
43
-
44
- export const SOOTSIM_STAGE_BACKGROUND_LABELS: Record<SootSimStageBackground, string> = {
45
- system: 'System',
46
- light: 'Light',
47
- dark: 'Dark',
48
- gray: 'Gray',
49
- }
50
-
51
- // render resolution: how many pixels peach renders per iphone point. one
52
- // setting with three states, carried by every host exactly like the ambient
53
- // glow level above: the menus, the persisted setting, the ?renderResolution=
54
- // boot seed, the live set-render-resolution push, electron's shared config.
55
- export type SootSimRenderResolution = 'high' | 'medium' | 'low'
56
- export const SOOTSIM_RENDER_RESOLUTION_LEVELS = ['high', 'medium', 'low'] as const
57
-
58
- // the installed desktop app presents the phone scaled to fit its window, so a
59
- // fixed 2 px/pt canvas is browser-resampled and its icons read soft. its
60
- // default is the level that tracks the pixels on screen; browsers and capture
61
- // keep medium, whose 2 px/pt is the deterministic capture contract.
62
- export const DESKTOP_RENDER_RESOLUTION: SootSimRenderResolution = 'low'
63
-
64
- export function isSootSimRenderResolution(
65
- value: unknown,
66
- ): value is SootSimRenderResolution {
67
- return value === 'high' || value === 'medium' || value === 'low'
68
- }
69
-
70
- export const SOOTSIM_RENDER_RESOLUTION_LABELS: Record<SootSimRenderResolution, string> = {
71
- high: 'High',
72
- medium: 'Medium',
73
- low: 'Low',
74
- }
75
-
76
- // screen effect: the real rendering, or one of the presentation looks the
77
- // engine's wireframe store draws. 'wireframe' is the clean structural sketch;
78
- // the others are its screen-space styles that ship as named effects.
79
- export type SootSimScreenEffect = 'none' | 'wireframe' | 'blueprint' | 'panel' | 'green'
80
- export const SOOTSIM_SCREEN_EFFECTS = [
81
- 'none',
82
- 'wireframe',
83
- 'blueprint',
84
- 'panel',
85
- 'green',
86
- ] as const
87
-
88
- export function isSootSimScreenEffect(value: unknown): value is SootSimScreenEffect {
89
- return (
90
- value === 'none' ||
91
- value === 'wireframe' ||
92
- value === 'blueprint' ||
93
- value === 'panel' ||
94
- value === 'green'
95
- )
96
- }
97
-
98
- export const SOOTSIM_SCREEN_EFFECT_LABELS: Record<SootSimScreenEffect, string> = {
99
- none: 'None',
100
- wireframe: 'Wireframe',
101
- blueprint: 'Blueprint',
102
- panel: 'Panel',
103
- green: 'Green',
104
- }
22
+ export * from './menuContract'
105
23
 
106
24
  export type SootSimMenuItemDef =
107
25
  | { type: 'separator' }
@@ -0,0 +1,102 @@
1
+ // the values and event names the menu protocol carries. kept apart from the menu builder so a
2
+ // runtime that only validates a setting or names an event does not carry the menus at boot.
3
+
4
+ export const SOOTSIM_ENGINE_EVENT_NAME = 'sootsim:engine-event'
5
+ export const SOOTSIM_SHELL_COMMAND_NAME = 'sootsim:shell-command'
6
+
7
+ export type SootSimMenuPlatform = 'browser' | 'electron'
8
+ export type SootSimScaleMode = 'physical' | 'point' | 'pixel' | 'fit'
9
+
10
+ // ambient glow strength. one setting with three states rather than an on/off
11
+ // pair plus a separate strength: every host that carries the preference (the
12
+ // menus, the persisted setting, the ?ambientGlow= boot seed, the live
13
+ // set-ambient-glow push, electron's shared config) then carries exactly one
14
+ // value and cannot end up half-configured.
15
+ export type SootSimAmbientGlowLevel = 'off' | 'soft' | 'full'
16
+ export const SOOTSIM_AMBIENT_GLOW_LEVELS = ['off', 'soft', 'full'] as const
17
+
18
+ export function isSootSimAmbientGlowLevel(
19
+ value: unknown,
20
+ ): value is SootSimAmbientGlowLevel {
21
+ return value === 'off' || value === 'soft' || value === 'full'
22
+ }
23
+
24
+ export const SOOTSIM_AMBIENT_GLOW_LABELS: Record<SootSimAmbientGlowLevel, string> = {
25
+ off: 'Off',
26
+ soft: 'Soft',
27
+ full: 'Full',
28
+ }
29
+
30
+ // the stage behind the device: system follows the mac's appearance (white in
31
+ // light mode, black in dark), or one pinned color. one value per host, like
32
+ // the glow level above.
33
+ export type SootSimStageBackground = 'system' | 'light' | 'dark' | 'gray'
34
+ export const SOOTSIM_STAGE_BACKGROUNDS = ['system', 'light', 'dark', 'gray'] as const
35
+
36
+ export function isSootSimStageBackground(
37
+ value: unknown,
38
+ ): value is SootSimStageBackground {
39
+ return value === 'system' || value === 'light' || value === 'dark' || value === 'gray'
40
+ }
41
+
42
+ export const SOOTSIM_STAGE_BACKGROUND_LABELS: Record<SootSimStageBackground, string> = {
43
+ system: 'System',
44
+ light: 'Light',
45
+ dark: 'Dark',
46
+ gray: 'Gray',
47
+ }
48
+
49
+ // render resolution: how many pixels peach renders per iphone point. one
50
+ // setting with three states, carried by every host exactly like the ambient
51
+ // glow level above: the menus, the persisted setting, the ?renderResolution=
52
+ // boot seed, the live set-render-resolution push, electron's shared config.
53
+ export type SootSimRenderResolution = 'high' | 'medium' | 'low'
54
+ export const SOOTSIM_RENDER_RESOLUTION_LEVELS = ['high', 'medium', 'low'] as const
55
+
56
+ // the installed desktop app presents the phone scaled to fit its window, so a
57
+ // fixed 2 px/pt canvas is browser-resampled and its icons read soft. its
58
+ // default is the level that tracks the pixels on screen; browsers and capture
59
+ // keep medium, whose 2 px/pt is the deterministic capture contract.
60
+ export const DESKTOP_RENDER_RESOLUTION: SootSimRenderResolution = 'low'
61
+
62
+ export function isSootSimRenderResolution(
63
+ value: unknown,
64
+ ): value is SootSimRenderResolution {
65
+ return value === 'high' || value === 'medium' || value === 'low'
66
+ }
67
+
68
+ export const SOOTSIM_RENDER_RESOLUTION_LABELS: Record<SootSimRenderResolution, string> = {
69
+ high: 'High',
70
+ medium: 'Medium',
71
+ low: 'Low',
72
+ }
73
+
74
+ // screen effect: the real rendering, or one of the presentation looks the
75
+ // engine's wireframe store draws. 'wireframe' is the clean structural sketch;
76
+ // the others are its screen-space styles that ship as named effects.
77
+ export type SootSimScreenEffect = 'none' | 'wireframe' | 'blueprint' | 'panel' | 'green'
78
+ export const SOOTSIM_SCREEN_EFFECTS = [
79
+ 'none',
80
+ 'wireframe',
81
+ 'blueprint',
82
+ 'panel',
83
+ 'green',
84
+ ] as const
85
+
86
+ export function isSootSimScreenEffect(value: unknown): value is SootSimScreenEffect {
87
+ return (
88
+ value === 'none' ||
89
+ value === 'wireframe' ||
90
+ value === 'blueprint' ||
91
+ value === 'panel' ||
92
+ value === 'green'
93
+ )
94
+ }
95
+
96
+ export const SOOTSIM_SCREEN_EFFECT_LABELS: Record<SootSimScreenEffect, string> = {
97
+ none: 'None',
98
+ wireframe: 'Wireframe',
99
+ blueprint: 'Blueprint',
100
+ panel: 'Panel',
101
+ green: 'Green',
102
+ }
@@ -394,7 +394,14 @@ export async function validateRNXMetroModulePaths(
394
394
  source: string,
395
395
  modulePaths: RNXMetroModulePathMap,
396
396
  ): Promise<void> {
397
- const layout = await readRNXMetroBundleLayout(source)
397
+ checkRNXMetroModulePaths(await readRNXMetroBundleLayout(source), modulePaths)
398
+ }
399
+
400
+ /** the identity contract against a layout the caller already read. */
401
+ export function checkRNXMetroModulePaths(
402
+ layout: RNXMetroBundleLayout,
403
+ modulePaths: RNXMetroModulePathMap,
404
+ ): void {
398
405
  if (layout.moduleIdFailures > 0) {
399
406
  throw new Error(
400
407
  `Metro module identity could not read ${layout.moduleIdFailures} module IDs`,
@@ -431,8 +438,14 @@ export async function validateRNXMetroModulePaths(
431
438
 
432
439
  export async function validateRNXMetroModuleIdentity(
433
440
  identity: RNXMetroModuleIdentity,
441
+ // the layout of the bundle this identity was read from. its footers follow
442
+ // the graph and register nothing, so its defines are the identity source's.
443
+ layout?: RNXMetroBundleLayout,
434
444
  ): Promise<void> {
435
- await validateRNXMetroModulePaths(identity.source, identity.modulePaths)
445
+ checkRNXMetroModulePaths(
446
+ layout ?? (await readRNXMetroBundleLayout(identity.source)),
447
+ identity.modulePaths,
448
+ )
436
449
  for (const moduleId of Object.keys(identity.logicalSpecifiers)) {
437
450
  if (!Object.prototype.hasOwnProperty.call(identity.modulePaths, moduleId)) {
438
451
  throw new Error(
@@ -2,14 +2,6 @@ import {
2
2
  DEFAULT_PROFILER_CATEGORIES,
3
3
  formatProfilerCategories,
4
4
  } from '@peach/globals/profiler'
5
- import {
6
- SOOTSIM_AMBIENT_GLOW_LEVELS,
7
- SOOTSIM_RENDER_RESOLUTION_LEVELS,
8
- SOOTSIM_STAGE_BACKGROUNDS,
9
- type SootSimAmbientGlowLevel,
10
- type SootSimRenderResolution,
11
- type SootSimStageBackground,
12
- } from 'peachsim/menu'
13
5
  import {
14
6
  type DeviceModel,
15
7
  type DevicePosture,
@@ -19,6 +11,14 @@ import {
19
11
  DYNAMIC_WINDOW_LIMITS,
20
12
  devices,
21
13
  } from './device-catalog.ts'
14
+ import {
15
+ SOOTSIM_AMBIENT_GLOW_LEVELS,
16
+ SOOTSIM_RENDER_RESOLUTION_LEVELS,
17
+ SOOTSIM_STAGE_BACKGROUNDS,
18
+ type SootSimAmbientGlowLevel,
19
+ type SootSimRenderResolution,
20
+ type SootSimStageBackground,
21
+ } from './menuContract'
22
22
  import { DEFAULT_REACT_NATIVE_CONFIGURATION } from './reactNativeConfigurationContract.ts'
23
23
  import {
24
24
  BACKGROUND_PRESET_NAMES,
@@ -437,6 +437,26 @@ const clangInterfaceHeader = (header: string): string =>
437
437
  .replace(/\b_TtC10Foundation\d+(NS\w+)\b/g, '$1')
438
438
  .replace(/\b__attribute__\(\(objc_subclassing_restricted\)\)\s*/g, '')
439
439
 
440
+ export function swiftHeaderWithSuperclassImports(
441
+ header: string,
442
+ interfaceFor: (name: string) => { file: string; header: string } | null,
443
+ ): string {
444
+ const superclasses = new Set(
445
+ [...header.matchAll(/@interface\s+\w+\s*:\s*(\w+)/g)].map((match) => match[1]!),
446
+ )
447
+ return header.replace(/^@import (\w+);$/gm, (statement, name: string) => {
448
+ const dependency = interfaceFor(name)
449
+ if (
450
+ !dependency ||
451
+ ![...swiftInterfaceDeclarations(dependency.header)].some((name) =>
452
+ superclasses.has(name),
453
+ )
454
+ )
455
+ return statement
456
+ return `${statement}\n#import ${JSON.stringify(dependency.file)}`
457
+ })
458
+ }
459
+
440
460
  /** the Objective-C protocols and classes a generated interface header defines. */
441
461
  export const swiftInterfaceDeclarations = (header: string): Set<string> =>
442
462
  new Set([
@@ -908,7 +928,7 @@ export function swiftOptionalFramework(name: string): boolean {
908
928
  // an import may carry attributes (`@_exported`, `@preconcurrency`) and a
909
929
  // declaration kind (`import struct Nuke.ImageRequest`).
910
930
  const SWIFT_IMPORT =
911
- /^\s*(?:@\w+(?:\([^)]*\))?\s+)*import\s+(?:(?:typealias|struct|class|enum|protocol|let|var|func)\s+)?([A-Za-z_][A-Za-z0-9_]*)/
931
+ /^\s*(?:@\w+(?:\([^)]*\))?\s+|(?:public|package|internal|fileprivate|private)\s+)*import\s+(?:(?:typealias|struct|class|enum|protocol|let|var|func)\s+)?([A-Za-z_][A-Za-z0-9_]*)/
912
932
 
913
933
  // three-valued (kleene) logic shared by the swift and c condition
914
934
  // evaluators: an undecided operand only decides when the other forces the
@@ -7437,6 +7457,58 @@ function compileSwiftProjectOnce(
7437
7457
  if (existsSync(linked) && statSync(linked).size <= 8)
7438
7458
  rmSync(linked, { force: true })
7439
7459
  report('compiling and linking Swift modules')
7460
+ const frameworkInterfaces = new Map<
7461
+ string,
7462
+ { file: string; header: string } | null
7463
+ >()
7464
+ const frameworkInterface = (
7465
+ name: string,
7466
+ ): { file: string; header: string } | null => {
7467
+ if (frameworkInterfaces.has(name)) return frameworkInterfaces.get(name)!
7468
+ const source = distribution
7469
+ ? path.join(frameworkRoot, 'modules', `${name}-Swift.h`)
7470
+ : path.join(
7471
+ prebuilt!.modules,
7472
+ '..',
7473
+ `${name}.build/include/${name}-Swift.h`,
7474
+ )
7475
+ if (!existsSync(source)) {
7476
+ frameworkInterfaces.set(name, null)
7477
+ return null
7478
+ }
7479
+ const headers = path.join(
7480
+ swiftSdkPaths(frameworkRoot, options.toolchainBin).darwin,
7481
+ `System/Library/Frameworks/${name}.framework/Headers`,
7482
+ )
7483
+ const declared = new Set<string>()
7484
+ if (existsSync(headers))
7485
+ for (const file of readdirSync(headers, {
7486
+ recursive: true,
7487
+ encoding: 'utf8',
7488
+ }))
7489
+ if (file.endsWith('.h'))
7490
+ for (const name of swiftInterfaceDeclarations(
7491
+ readFileSync(path.join(headers, file), 'utf8'),
7492
+ ))
7493
+ declared.add(name)
7494
+ const header = withoutSwiftDeclarations(
7495
+ clangInterfaceHeader(readFileSync(source, 'utf8')),
7496
+ declared,
7497
+ `#import <${name}/${name}.h>`,
7498
+ )
7499
+ const value = {
7500
+ file: path.join(root, '.build/peach-framework-objc', `${name}-Swift.h`),
7501
+ header,
7502
+ }
7503
+ frameworkInterfaces.set(name, value)
7504
+ writeBuildFile(
7505
+ value.file,
7506
+ (existsSync(path.join(headers, `${name}.h`))
7507
+ ? `#import <${name}/${name}.h>\n`
7508
+ : '') + swiftHeaderWithSuperclassImports(header, frameworkInterface),
7509
+ )
7510
+ return value
7511
+ }
7440
7512
  try {
7441
7513
  // clang consumes the real emitted headers after swift finishes.
7442
7514
  // swiftpm's transitive swift module maps are not C declarations:
@@ -7475,7 +7547,10 @@ function compileSwiftProjectOnce(
7475
7547
  writeBuildFile(
7476
7548
  emitted,
7477
7549
  withoutSwiftDeclarations(
7478
- clangInterfaceHeader(readFileSync(original, 'utf8')),
7550
+ swiftHeaderWithSuperclassImports(
7551
+ clangInterfaceHeader(readFileSync(original, 'utf8')),
7552
+ frameworkInterface,
7553
+ ),
7479
7554
  new Set(preparation.nativeHeaderDeclarations[producer]),
7480
7555
  `#import <${producer}/${producer}.h>`,
7481
7556
  ),
package/src/swift-host.ts CHANGED
@@ -132,8 +132,10 @@ import {
132
132
  } from './swiftContract'
133
133
  import {
134
134
  enclosingXcodeWorkspace,
135
+ expandXcodeBuildSetting,
135
136
  readXcodeAppTarget,
136
137
  resolveXcodeSwiftPackages,
138
+ xcodeFileListEntries,
137
139
  type XcodeAppTarget,
138
140
  type XcodeModuleTarget,
139
141
  } from './xcode-project'
@@ -2312,11 +2314,34 @@ export function buildSwiftBundleFiles(
2312
2314
  CONTENTS_FOLDER_PATH: wrapper,
2313
2315
  UNLOCALIZED_RESOURCES_FOLDER_PATH: wrapper,
2314
2316
  INFOPLIST_PATH: `${wrapper}/Info.plist`,
2315
- SCRIPT_INPUT_FILE_COUNT: '0',
2316
- SCRIPT_INPUT_FILE_LIST_COUNT: '0',
2317
+ SCRIPT_INPUT_FILE_COUNT: String(phase.inputPaths.length),
2318
+ SCRIPT_INPUT_FILE_LIST_COUNT: String(phase.inputFileListPaths.length),
2317
2319
  SCRIPT_OUTPUT_FILE_COUNT: '0',
2318
2320
  SCRIPT_OUTPUT_FILE_LIST_COUNT: '0',
2319
2321
  }
2322
+ const inputPath = (raw: string) => {
2323
+ const file = path.resolve(
2324
+ phase.directory,
2325
+ expandXcodeBuildSetting(raw, (key) => env[key] ?? ''),
2326
+ )
2327
+ if (!fs.existsSync(file))
2328
+ throw new Error(
2329
+ `${xcode.app.name}: Run Script "${phase.name}" missing input ${file}`,
2330
+ )
2331
+ if (!file.startsWith(`${dir}${path.sep}`))
2332
+ watched.push(path.relative(packagePath, file).split(path.sep).join('/'))
2333
+ return file
2334
+ }
2335
+ phase.inputPaths.forEach((raw, index) => {
2336
+ env[`SCRIPT_INPUT_FILE_${index}`] = inputPath(raw)
2337
+ })
2338
+ phase.inputFileListPaths.forEach((raw, index) => {
2339
+ const source = inputPath(raw)
2340
+ const entries = xcodeFileListEntries(source, inputPath)
2341
+ const expanded = path.join(dir, `Input-${index}.xcfilelist`)
2342
+ fs.writeFileSync(expanded, `${entries.join('\n')}\n`)
2343
+ env[`SCRIPT_INPUT_FILE_LIST_${index}`] = expanded
2344
+ })
2320
2345
  try {
2321
2346
  const [shell, ...args] = phase.command
2322
2347
  execFileSync(shell, args, {
@@ -1529,6 +1529,7 @@ export function emitSwiftStandin(
1529
1529
  ].includes(name)
1530
1530
  ) &&
1531
1531
  !(name === nsObjectProtocol && externalRoot(id) === nsObject) &&
1532
+ !options.unconformed?.get(path)?.has(name.split(/::|\./).pop()!) &&
1532
1533
  !options.unconformed?.get(path)?.has(name),
1533
1534
  ),
1534
1535
  ].filter((name, at, all) => all.indexOf(name) === at)
@@ -1703,6 +1703,14 @@ function graphStandin(
1703
1703
  unconformed.set(owner, new Set([...(unconformed.get(owner) ?? []), conformance]))
1704
1704
  continue
1705
1705
  }
1706
+ // a superclass another module declares already brings this conformance
1707
+ const redundant = /^redundant conformance of '\w+' to protocol '(\w+)'$/.exec(
1708
+ match[3]!,
1709
+ )?.[1]
1710
+ if (owner && redundant) {
1711
+ unconformed.set(owner, new Set([...(unconformed.get(owner) ?? []), redundant]))
1712
+ continue
1713
+ }
1706
1714
  if (owner && swiftPaths.has(owner))
1707
1715
  throw new Error(
1708
1716
  `Swift declaration ${module}.${owner} does not typecheck: ${match[3]}`,
@@ -180,6 +180,8 @@ export interface XcodeAppTarget extends XcodeModuleTarget {
180
180
  command: [string, ...string[]]
181
181
  directory: string
182
182
  environment: Record<string, string>
183
+ inputPaths: string[]
184
+ inputFileListPaths: string[]
183
185
  }[]
184
186
  appIconName: string
185
187
  /** the app project PBXProject developmentRegion, xcode's "en" when it declares none. */
@@ -890,6 +892,8 @@ function compilationConditions(settings: (key: string) => string): string[] {
890
892
  ]
891
893
  }
892
894
 
895
+ export { expand as expandXcodeBuildSetting }
896
+
893
897
  function expand(value: string, lookup: (name: string) => string): string {
894
898
  return value.replace(
895
899
  /\$(?:\(([A-Za-z_][A-Za-z0-9_]*)((?::[^)]*)?)\)|\{([A-Za-z_][A-Za-z0-9_]*)((?::[^}]*)?)\}|([A-Za-z_][A-Za-z0-9_]*))/g,
@@ -1013,6 +1017,8 @@ function shellCopies(
1013
1017
  }
1014
1018
 
1015
1019
  // an xcfilelist names one path per line; blank lines and comments are skipped.
1020
+ export { fileListEntries as xcodeFileListEntries }
1021
+
1016
1022
  function fileListEntries(file: string, absolute: (raw: string) => string): string[] {
1017
1023
  return fs
1018
1024
  .readFileSync(file, 'utf8')
@@ -2851,8 +2857,6 @@ export function readXcodeAppTarget(
2851
2857
  const phase = app.project.object(id)
2852
2858
  if (
2853
2859
  phase?.isa !== 'PBXShellScriptBuildPhase' ||
2854
- list(phase.inputPaths).length ||
2855
- list(phase.inputFileListPaths).length ||
2856
2860
  list(phase.outputPaths).length ||
2857
2861
  list(phase.outputFileListPaths).length
2858
2862
  )
@@ -2864,6 +2868,8 @@ export function readXcodeAppTarget(
2864
2868
  str(phase.shellScript) ?? '',
2865
2869
  ),
2866
2870
  directory: app.project.projectDir,
2871
+ inputPaths: list(phase.inputPaths),
2872
+ inputFileListPaths: list(phase.inputFileListPaths),
2867
2873
  environment: Object.fromEntries(
2868
2874
  [...settingKeys]
2869
2875
  .filter((key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key))