peachsim 0.1.2220 → 0.1.2222

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 (128) hide show
  1. package/cli/commands/compat.ts +4 -4
  2. package/cli/commands/inspect.ts +4 -4
  3. package/cli/commands/report-issue.ts +1 -1
  4. package/cli/runtime-compat.ts +7 -2
  5. package/dist-lib/agent-daemon-client.cjs +1 -1
  6. package/dist-lib/agent-events.cjs +1 -1
  7. package/dist-lib/attached-projects.cjs +1 -1
  8. package/dist-lib/auth/shared-session.cjs +1 -1
  9. package/dist-lib/backend-origin.cjs +1 -1
  10. package/dist-lib/beta.cjs +1 -1
  11. package/dist-lib/beta.mjs +1 -1
  12. package/dist-lib/bridge-constants.cjs +1 -1
  13. package/dist-lib/bridge-contract-input.cjs +1 -1
  14. package/dist-lib/bridge-contract-input.mjs +1 -1
  15. package/dist-lib/bridge-contract.cjs +1 -1
  16. package/dist-lib/bridge-contract.mjs +1 -1
  17. package/dist-lib/bundle-channel.cjs +475 -381
  18. package/dist-lib/capture-contract.cjs +1 -1
  19. package/dist-lib/capture-contract.mjs +1 -1
  20. package/dist-lib/cloud-contract.cjs +1 -1
  21. package/dist-lib/cloud-contract.mjs +1 -1
  22. package/dist-lib/cloud-test-args.cjs +1 -1
  23. package/dist-lib/cloud-test-args.mjs +1 -1
  24. package/dist-lib/cloud.cjs +1 -1
  25. package/dist-lib/cloud.mjs +1 -1
  26. package/dist-lib/config.cjs +1 -1
  27. package/dist-lib/config.mjs +1 -1
  28. package/dist-lib/detox/index.cjs +3 -2
  29. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  30. package/dist-lib/expo-swift-bundle.cjs +472 -379
  31. package/dist-lib/expo-swift-metadata.cjs +1 -1
  32. package/dist-lib/expo-swift-metadata.mjs +1 -1
  33. package/dist-lib/home-paths.cjs +1 -1
  34. package/dist-lib/host/bridge-host.cjs +695 -574
  35. package/dist-lib/host/fetch-proxy-handler.cjs +10 -7
  36. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  37. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  38. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  39. package/dist-lib/host/websocket-proxy.cjs +1 -1
  40. package/dist-lib/index.cjs +8232 -7815
  41. package/dist-lib/jump-to-source-babel.cjs +1 -1
  42. package/dist-lib/jump-to-source-native.cjs +1 -1
  43. package/dist-lib/kotlin-host.cjs +1 -1
  44. package/dist-lib/menu.cjs +1 -1
  45. package/dist-lib/menu.mjs +1 -1
  46. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  47. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  48. package/dist-lib/metro-production-bundle.cjs +1 -1
  49. package/dist-lib/metro-production-bundle.mjs +1 -1
  50. package/dist-lib/metro.cjs +8146 -7737
  51. package/dist-lib/native-app-identity-project.cjs +331 -13
  52. package/dist-lib/native-app-identity-project.mjs +331 -13
  53. package/dist-lib/native-app-identity.cjs +16 -3
  54. package/dist-lib/native-app-identity.mjs +16 -3
  55. package/dist-lib/native-firebase-metadata.cjs +1 -1
  56. package/dist-lib/native-firebase-metadata.mjs +1 -1
  57. package/dist-lib/pdfium-raster.cjs +1 -1
  58. package/dist-lib/pdfium-raster.mjs +1 -1
  59. package/dist-lib/plist.cjs +1 -1
  60. package/dist-lib/plist.mjs +1 -1
  61. package/dist-lib/profiles.cjs +1 -1
  62. package/dist-lib/public-brand.cjs +1 -1
  63. package/dist-lib/public-origins-lib-esm.json +25 -24
  64. package/dist-lib/public-origins-lib.json +99 -96
  65. package/dist-lib/react-native-host-modules.cjs +1 -1
  66. package/dist-lib/react-native-host-modules.mjs +1 -1
  67. package/dist-lib/reap-browser.cjs +1 -1
  68. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  69. package/dist-lib/sdk.cjs +1 -1
  70. package/dist-lib/sdk.mjs +1 -1
  71. package/dist-lib/skills.cjs +532 -420
  72. package/dist-lib/swift-compile.cjs +84 -49
  73. package/dist-lib/swift-compile.mjs +84 -49
  74. package/dist-lib/swift-package.cjs +1 -1
  75. package/dist-lib/swift-package.mjs +1 -1
  76. package/dist-lib/swift-submit.cjs +1 -1
  77. package/dist-lib/swift-submit.mjs +1 -1
  78. package/dist-lib/swift-toolchain.cjs +1 -1
  79. package/dist-lib/swift.cjs +1 -1
  80. package/dist-lib/vite-swift.cjs +158 -69
  81. package/dist-lib/vite.cjs +2930 -2800
  82. package/dist-lib/worklet-executable.cjs +1 -1
  83. package/dist-lib/worklet-executable.mjs +1 -1
  84. package/dist-types/packages/peach-engine/src/cli-companion.d.ts +1 -0
  85. package/dist-types/packages/peach-engine/src/cli-host-terser.d.ts +7 -0
  86. package/dist-types/packages/peach-engine/src/engine/glass-material.d.ts +1 -1
  87. package/dist-types/packages/peach-engine/src/engine/liquid-glass.d.ts +1 -0
  88. package/dist-types/packages/peach-engine/src/engine/native-layout.d.ts +1 -0
  89. package/dist-types/packages/peach-engine/src/engine/node.d.ts +5 -0
  90. package/dist-types/packages/peach-engine/src/engine/scene-table.d.ts +8 -1
  91. package/dist-types/packages/peach-engine/src/engine/types.d.ts +3 -0
  92. package/dist-types/packages/peach-engine/src/engine/yoga-layout.d.ts +1 -1
  93. package/dist-types/packages/peach-engine/src/remote-stream/cloud-display.d.ts +2 -2
  94. package/dist-types/packages/peach-engine/src/render-worker/compositor-protocol.d.ts +1 -0
  95. package/dist-types/packages/peach-engine/src/render-worker/shell-tree-wire.d.ts +1 -2
  96. package/dist-types/packages/peach-kit/src/internal.d.ts +1 -0
  97. package/dist-types/packages/peach-kit/src/ios-tint.d.ts +1 -0
  98. package/dist-types/packages/peach-kit/src/transitions/screen-transition-math.d.ts +25 -0
  99. package/kotlin/sdk.tar.gz +0 -0
  100. package/package.json +1 -1
  101. package/runtime-companion.json +1 -1
  102. package/sdk/ios/System/Library/Frameworks/SceneKit.framework/Headers/SCNAnimation.h +4 -4
  103. package/sdk/ios/System/Library/Frameworks/SceneKit.framework/Headers/SCNCameraController.h +1 -1
  104. package/sdk/ios/System/Library/Frameworks/SceneKit.framework/Headers/SCNConstraint.h +5 -5
  105. package/sdk/ios/System/Library/Frameworks/SceneKit.framework/Headers/SCNGeometry.h +1 -1
  106. package/sdk/ios/System/Library/Frameworks/SceneKit.framework/Headers/SCNLevelOfDetail.h +1 -1
  107. package/sdk/ios/System/Library/Frameworks/SceneKit.framework/Headers/SCNParametricGeometry.h +9 -9
  108. package/sdk/ios/System/Library/Frameworks/SceneKit.framework/Headers/SCNParticleSystem.h +1 -1
  109. package/sdk/ios/System/Library/Frameworks/SceneKit.framework/Headers/SCNPhysicsBehavior.h +7 -7
  110. package/sdk/ios/System/Library/Frameworks/SceneKit.framework/Headers/SCNRenderer.h +1 -1
  111. package/sdk/ios/System/Library/Frameworks/SceneKit.framework/Headers/SCNShadable.h +1 -1
  112. package/sdk/ios/System/Library/Frameworks/SceneKit.framework/Headers/SCNTechnique.h +1 -1
  113. package/sdk/ios/System/Library/Frameworks/UIKit.framework/Headers/NSLayoutConstraint.h +1 -8
  114. package/sdk/ios/System/Library/Frameworks/UIKit.framework/Modules/module.modulemap +1 -0
  115. package/sdk/ios/System/Library/Frameworks/UIKitTypes.framework/Headers/UIKitTypes.h +1 -0
  116. package/sdk/ios/System/Library/Frameworks/UIKitTypes.framework/Headers/UILayoutPriority.h +11 -0
  117. package/src/cli-runtime.ts +1 -0
  118. package/src/expo-native-workspace.ts +14 -10
  119. package/src/expo-workspace-exclusion.ts +10 -7
  120. package/src/react-native-autolinking.ts +2 -1
  121. package/src/runtime-delivery.ts +7 -0
  122. package/src/swift-compile.ts +90 -54
  123. package/src/swift-coverage/xcode-assets.ts +4 -2
  124. package/src/swift-host.ts +53 -14
  125. package/src/swift-objc-import.ts +4 -0
  126. package/src/wasm-optimize.ts +5 -1
  127. package/src/xcode-project.ts +26 -5
  128. package/src/xcode-target-members.ts +25 -3
@@ -7,7 +7,7 @@ import path from 'node:path'
7
7
  import { promisify } from 'node:util'
8
8
  import { detectPackageManager } from '../cli/commands/detect'
9
9
  import { teamMachineInstalled } from './admitted-mac-build'
10
- import { workspaceExclusion } from './expo-workspace-exclusion'
10
+ import { workspaceExclusion, type WorkspaceExclusion } from './expo-workspace-exclusion'
11
11
  import { cacheDir } from './home-paths'
12
12
  import { failedOutputLine } from './native-failure-output'
13
13
  import { requireNativeGit } from './native-git'
@@ -49,7 +49,7 @@ export function reactNativePodsEnvironment() {
49
49
  function workspacePackageManager(
50
50
  root: string,
51
51
  declared: unknown,
52
- excluded: (relative: string) => boolean,
52
+ excluded: WorkspaceExclusion,
53
53
  ): string {
54
54
  const manager = typeof declared === 'string' ? declared : detectPackageManager(root)
55
55
  const locks = [
@@ -116,7 +116,7 @@ export async function verifyExpoWorkspaceCopy(app: string, work: string) {
116
116
  for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
117
117
  const file = path.join(directory, entry.name)
118
118
  const relative = path.relative(root, file)
119
- if (excluded(relative)) continue
119
+ if (excluded(relative, entry.isDirectory())) continue
120
120
  const copy = path.join(work, relative)
121
121
  if (entry.isDirectory()) walk(file)
122
122
  else if (entry.isSymbolicLink()) {
@@ -212,7 +212,7 @@ async function hashWorkspaceInputs(
212
212
  hash: string
213
213
  // the subset of inputs that decides the prepared workspace
214
214
  nativeHash: string
215
- excluded: (relative: string) => boolean
215
+ excluded: WorkspaceExclusion
216
216
  }> {
217
217
  const root = findWorkspaceRoot(app)
218
218
  const excluded = await workspaceExclusion(
@@ -286,7 +286,7 @@ async function hashWorkspaceInputs(
286
286
  signal?.throwIfAborted()
287
287
  const file = path.join(directory, entry.name)
288
288
  const relative = path.relative(root, file)
289
- if (excluded(relative)) continue
289
+ if (excluded(relative, entry.isDirectory())) continue
290
290
  hash.update(`${relative}\0`)
291
291
  if (entry.isSymbolicLink()) {
292
292
  const link = `link\0${await fs.promises.readlink(file)}\0`
@@ -446,7 +446,7 @@ function syncChangedFiles(
446
446
  root: string,
447
447
  work: string,
448
448
  appRelative: string,
449
- excluded: (relative: string) => boolean,
449
+ excluded: WorkspaceExclusion,
450
450
  installed: boolean,
451
451
  ) {
452
452
  const copyIfNewer = (file: string, relative: string, newerOnly: boolean) => {
@@ -464,8 +464,12 @@ function syncChangedFiles(
464
464
  )
465
465
  return
466
466
  fs.mkdirSync(path.dirname(target), { recursive: true })
467
- fs.copyFileSync(file, target)
468
- fs.utimesSync(target, source.atime, source.mtime)
467
+ // a tool already running from the copy (the autolinker) must never read a
468
+ // truncated script, so the new bytes land by rename
469
+ const staged = `${target}.${process.pid}.peach-sync`
470
+ fs.copyFileSync(file, staged)
471
+ fs.utimesSync(staged, source.atime, source.mtime)
472
+ fs.renameSync(staged, target)
469
473
  }
470
474
  const walkInstalled = (directory: string) => {
471
475
  for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
@@ -490,7 +494,7 @@ function syncChangedFiles(
490
494
  }
491
495
  // the app's ios project is prebuild output, not a source
492
496
  if (
493
- excluded(relative) ||
497
+ excluded(relative, entry.isDirectory()) ||
494
498
  entry.isSymbolicLink() ||
495
499
  (!sourceIos && relative === path.join(appRelative, 'ios'))
496
500
  )
@@ -640,7 +644,7 @@ function prepare(app: string, stage: 'project' | 'workspace', signal?: AbortSign
640
644
  async function copyInstalledWorkspace(
641
645
  root: string,
642
646
  destination: string,
643
- excluded: (relative: string) => boolean,
647
+ excluded: WorkspaceExclusion,
644
648
  ) {
645
649
  const installed: string[] = []
646
650
  await fs.promises.cp(root, destination, {
@@ -4,6 +4,8 @@ import path from 'node:path'
4
4
  import { promisify } from 'node:util'
5
5
  import ignore from 'ignore'
6
6
 
7
+ export type WorkspaceExclusion = (relative: string, isDirectory?: boolean) => boolean
8
+
7
9
  // eas build archives a repository without its gitignored paths, so the prepare
8
10
  // neither copies nor hashes them: files a dev server generates into an ignored
9
11
  // directory never invalidate the prepared workspace.
@@ -13,7 +15,7 @@ export async function workspaceExclusion(
13
15
  root: string,
14
16
  portable = false,
15
17
  linkedPackages: string[] = [],
16
- ) {
18
+ ): Promise<WorkspaceExclusion> {
17
19
  const listed = await promisify(execFile)(
18
20
  'git',
19
21
  ['ls-files', '-z', '--others', '--ignored', '--exclude-standard', '--directory'],
@@ -34,8 +36,8 @@ export async function workspaceExclusion(
34
36
  .filter(Boolean)
35
37
  .map((entry) => entry.replace(/\/$/, '')),
36
38
  )
37
- const ignoreFiles = new Map<string, ReturnType<typeof ignore>>()
38
- return (relative: string) => {
39
+ const ignoreFiles = new Map<string, ReturnType<typeof ignore> | null>()
40
+ return (relative, knownDirectory) => {
39
41
  const parts = relative.split(path.sep)
40
42
  const linked = linkedPackages.some((directory) =>
41
43
  relative.startsWith(directory + path.sep),
@@ -47,16 +49,17 @@ export async function workspaceExclusion(
47
49
  }
48
50
  if (portable && relative) {
49
51
  let excluded = false
50
- const isDirectory = fs.lstatSync(path.join(root, relative)).isDirectory()
52
+ const isDirectory =
53
+ knownDirectory ?? fs.lstatSync(path.join(root, relative)).isDirectory()
51
54
  for (let index = 0; index < parts.length; index++) {
52
55
  const directory = parts.slice(0, index).join(path.sep)
53
56
  let rules = ignoreFiles.get(directory)
54
- if (!rules) {
55
- rules = ignore()
57
+ if (rules === undefined) {
56
58
  const file = path.join(root, directory, '.gitignore')
57
- if (fs.existsSync(file)) rules.add(fs.readFileSync(file, 'utf8'))
59
+ rules = fs.existsSync(file) ? ignore().add(fs.readFileSync(file, 'utf8')) : null
58
60
  ignoreFiles.set(directory, rules)
59
61
  }
62
+ if (!rules) continue
60
63
  const item = parts.slice(index).join('/')
61
64
  const result = rules.test(isDirectory ? `${item}/` : item)
62
65
  if (result.ignored) excluded = true
@@ -29,6 +29,7 @@ async function cliAdvertises(
29
29
  appRoot: string,
30
30
  signal?: AbortSignal,
31
31
  ): Promise<boolean> {
32
+ const started = Date.now()
32
33
  const { stdout, stderr } = await run('node', [cli, ...args], {
33
34
  cwd: appRoot,
34
35
  signal,
@@ -38,7 +39,7 @@ async function cliAdvertises(
38
39
  })
39
40
  if (!stdout.trim())
40
41
  throw new Error(
41
- `${cli} ${args.join(' ')} returned empty help output${stderr.trim() ? `: ${stderr.trim()}` : ''}`,
42
+ `${cli} ${args.join(' ')} exited 0 after ${Date.now() - started}ms with empty help output${stderr.trim() ? `: ${stderr.trim()}` : ''}`,
42
43
  )
43
44
  return stdout.includes(token)
44
45
  }
@@ -39,6 +39,13 @@ export const RNX_RUNTIME_PRODUCT: RuntimeProduct = {
39
39
  // vite/metro plugins serve, so its absence means the tarball is not one.
40
40
  installedMarker: 'index.html',
41
41
  verifyInstall: ensureCliCompanion,
42
+ // canvaskit's ganesh backend is a runtime pack because a browser with WebGPU
43
+ // never runs it, but linux and CI browsers usually have no GPU and always do
44
+ afterInstall: async (dir) => {
45
+ if (process.platform !== 'linux' && !process.env.CI) return
46
+ const { ensureRuntimePackFor } = await import('./runtime-packs.ts')
47
+ await ensureRuntimePackFor(dir, '/engine/canvaskit-ganesh.wasm')
48
+ },
42
49
  env: {
43
50
  cdnOrigin: 'PEACH_CDN_ORIGIN',
44
51
  channel: 'PEACH_RUNTIME_CHANNEL',
@@ -140,7 +140,7 @@ import {
140
140
  SWIFT_FRAMEWORK_PRODUCT_FILE,
141
141
  swiftFrameworkProductSchema,
142
142
  } from './swiftRuntimeProductContract'
143
- import { optimizeWasm, WASM_OPTIMIZE_IDENTITY } from './wasm-optimize'
143
+ import { KEEP_WASM_NAMES, optimizeWasm, WASM_OPTIMIZE_IDENTITY } from './wasm-optimize'
144
144
  import { writeBuildFile } from './write-build-file'
145
145
  import { writeXcodeHeaderSearch } from './xcode-header-search'
146
146
  import type { SwiftImageUpload } from './swift-submit'
@@ -424,11 +424,14 @@ export function swiftModuleInterface(
424
424
  // the compiler imports the module that defines a type, and UIKit's declarations
425
425
  // live in UIKitCore/UIKitText, which only the umbrella UIKit module represents
426
426
  // to clang. CoreFoundation's CGFloat lives in the Swift CoreFoundation overlay,
427
- // while clang's Foundation umbrella provides CoreFoundation/CoreFoundation.h.
427
+ // while clang's Foundation umbrella also owns networking class declarations.
428
428
  const clangInterfaceHeader = (header: string): string =>
429
429
  header
430
430
  .replace(/^@import (?:UIKitCore|UIKitText);$/gm, '@import UIKit;')
431
- .replace(/^@import CoreFoundation;$/gm, '@import Foundation;')
431
+ .replace(
432
+ /^@import (?:CoreFoundation|FoundationNetworking);$/gm,
433
+ '@import Foundation;',
434
+ )
432
435
  .replace(/\b_TtC10Foundation\d+(NS\w+)\b/g, '$1')
433
436
  .replace(/\b__attribute__\(\(objc_subclassing_restricted\)\)\s*/g, '')
434
437
 
@@ -836,7 +839,8 @@ const OPTIONAL_FRAMEWORK_DEPENDENCIES: Record<string, string[]> = {
836
839
  PeachCFNetwork: [],
837
840
  JavaScriptCore: ['PeachJavaScriptCore'],
838
841
  PeachJavaScriptCore: [],
839
- GameplayKit: [],
842
+ // GameplayKit names SpriteKit and SceneKit nodes, as on iOS
843
+ GameplayKit: ['SpriteKit', 'SceneKit', 'PeachBox2D', 'simd', 'GLKit'],
840
844
  simd: [],
841
845
  ModelIO: ['simd'],
842
846
  GLKit: ['ModelIO'],
@@ -1593,14 +1597,25 @@ export function swiftTargetDependencies(
1593
1597
  }
1594
1598
 
1595
1599
  const ONE_PRODUCT = '.product(name: "One", package: "sootsim-swift")'
1596
- const SPRITEKIT_PRODUCT = '.product(name: "SpriteKit", package: "sootsim-swift")'
1597
- // SpriteKit is an optional family (plans/peach/spritekit.md): it links only
1598
- // into an app whose sources import it, so no other artifact carries it.
1599
- const importsSpriteKit = (files: Record<string, string>): boolean =>
1600
- Object.entries(files).some(
1601
- ([file, contents]) =>
1602
- file.endsWith('.swift') && swiftImportNames(contents).includes('SpriteKit'),
1600
+ // the game family (plans/peach/spritekit.md, plans/peach/scenekit.md) links
1601
+ // only into an app whose sources import it, so no other artifact carries it.
1602
+ // SceneKit names SpriteKit, so importing it links both.
1603
+ const GAME_FAMILY = ['SpriteKit', 'SceneKit'] as const
1604
+ const gameProducts = (files: Record<string, string>): string[] => {
1605
+ const imported = new Set(
1606
+ Object.entries(files).flatMap(([file, contents]) =>
1607
+ file.endsWith('.swift') ? swiftImportNames(contents) : [],
1608
+ ),
1603
1609
  )
1610
+ if (imported.has('SceneKit')) return [...GAME_FAMILY]
1611
+ return imported.has('SpriteKit') ? ['SpriteKit'] : []
1612
+ }
1613
+ const gameProduct = (name: string) =>
1614
+ `.product(name: "${name}", package: "sootsim-swift")`
1615
+ const GAME_REACH: Record<string, string[]> = {
1616
+ SpriteKit: ['SpriteKit', 'PeachBox2D', 'simd', 'GLKit'],
1617
+ SceneKit: ['SceneKit'],
1618
+ }
1604
1619
  // every iOS source sees through Foundation what darwin's Foundation exports:
1605
1620
  // PeachObjC supplies the ObjectiveC module's selectors, Dispatch Foundation's
1606
1621
  // Darwin queue exports, and FoundationNetworking (re-exporting PeachFoundation)
@@ -1672,6 +1687,7 @@ export function swiftArtifactHash(
1672
1687
  hash.update(`swift-compile\n${release}\n`)
1673
1688
  hash.update(compilerSourceDigest)
1674
1689
  if (input.oneModule) hash.update('one-module\n')
1690
+ if (KEEP_WASM_NAMES) hash.update('names\n')
1675
1691
  hash.update(swiftModuleDigest(moduleRoot))
1676
1692
  if (input.optimize) hash.update(`optimize\n${WASM_OPTIMIZE_IDENTITY}\n`)
1677
1693
  for (const file of Object.keys(input.files).sort()) {
@@ -1853,12 +1869,13 @@ export function emitSwiftPackageManifest(args: {
1853
1869
  nativeFlags?: Map<string, string[]>
1854
1870
  nativeImportFlags?: Map<string, string[]>
1855
1871
  nativeHeaderFlags?: string[]
1872
+ nativeSwiftHeaderMaps?: ReadonlyMap<string, string>
1856
1873
  nativeHeaderDeclarations?: Readonly<Record<string, readonly string[]>>
1857
1874
  objcImplementationModules?: Set<string>
1858
1875
  siblings: Map<string, string[]>
1859
1876
  moduleRoot: string
1860
1877
  oneModule?: boolean
1861
- spriteKit?: boolean
1878
+ gameProducts?: string[]
1862
1879
  /** link the prebuilt distribution at moduleRoot instead of its sources. */
1863
1880
  distribution?: SwiftDistributionManifest
1864
1881
  /** prebuilt interfaces owned by local source or system-library targets. */
@@ -1883,7 +1900,14 @@ export function emitSwiftPackageManifest(args: {
1883
1900
  /** the development region SwiftPM needs as defaultLocalization beside localized resources. */
1884
1901
  defaultLocalization?: string
1885
1902
  }): string {
1886
- const { targets, siblings, moduleRoot, oneModule, spriteKit, distribution } = args
1903
+ const {
1904
+ targets,
1905
+ siblings,
1906
+ moduleRoot,
1907
+ oneModule,
1908
+ gameProducts = [],
1909
+ distribution,
1910
+ } = args
1887
1911
  const prebuilt = args.prebuilt
1888
1912
  // a distribution and a separately built framework package both link objects
1889
1913
  // instead of compiling the framework sources into this package's scratch.
@@ -1930,9 +1954,8 @@ export function emitSwiftPackageManifest(args: {
1930
1954
  for (const name of [...siblings.values()].flat())
1931
1955
  if (OPTIONAL_FRAMEWORKS.has(name) && !targets.some((target) => target.name === name))
1932
1956
  reach(name)
1933
- if (spriteKit) {
1934
- for (const name of ['SpriteKit', 'PeachBox2D', 'simd', 'GLKit']) reach(name)
1935
- }
1957
+ for (const product of gameProducts)
1958
+ for (const name of GAME_REACH[product] ?? []) reach(name)
1936
1959
  // the distribution builder names each object lib/<target>-<file>.o.
1937
1960
  // preserve link order; only reached framework registrants link eagerly.
1938
1961
  // the list is named by its content: SwiftPM relinks when the linker
@@ -1968,7 +1991,7 @@ export function emitSwiftPackageManifest(args: {
1968
1991
  const objects = available.filter(
1969
1992
  (object) =>
1970
1993
  !targets.some((target) => object.startsWith(`lib/${target.name}-`)) &&
1971
- [...OPTIONAL_FRAMEWORKS, 'SpriteKit', 'PeachBox2D'].every(
1994
+ [...OPTIONAL_FRAMEWORKS, ...GAME_FAMILY, 'PeachBox2D'].every(
1972
1995
  (name) => !object.startsWith(`lib/${name}-`) || reachedFrameworks.has(name),
1973
1996
  ),
1974
1997
  )
@@ -2163,6 +2186,11 @@ export function emitSwiftPackageManifest(args: {
2163
2186
  includeImportContext(dependency)
2164
2187
  const importedNativeFlags = [
2165
2188
  ...(args.nativeImportFlags?.get(target.name) ?? []),
2189
+ ...[...importContext].flatMap((name) => {
2190
+ const map =
2191
+ name === target.name ? undefined : args.nativeSwiftHeaderMaps?.get(name)
2192
+ return map ? ['-fmodule-map-file=' + map] : []
2193
+ }),
2166
2194
  ...[...importContext].flatMap((name) => args.native?.get(name)?.importFlags ?? []),
2167
2195
  ...[...importContext].flatMap((name) => {
2168
2196
  const flags = targets.find((target) => target.name === name)?.swiftFlags ?? []
@@ -2239,7 +2267,7 @@ export function emitSwiftPackageManifest(args: {
2239
2267
  : [
2240
2268
  '.product(name: "SwiftUI", package: "sootsim-swift")',
2241
2269
  ...(oneModule ? [ONE_PRODUCT] : []),
2242
- ...(spriteKit ? [SPRITEKIT_PRODUCT] : []),
2270
+ ...gameProducts.map(gameProduct),
2243
2271
  ]),
2244
2272
  ...(siblings.get(target.name) ?? []).flatMap((dependency) =>
2245
2273
  clang &&
@@ -2293,7 +2321,7 @@ export function emitSwiftPackageManifest(args: {
2293
2321
  .unsafeFlags(${flagArray(`[
2294
2322
  ${REACTOR_FLAG}${executableLinkFlags(args.root)} "-Xlinker", "--export-if-defined=__main_argc_argv"${library},
2295
2323
  ]`)}),
2296
- .unsafeFlags(["-Xlinker", "--strip-all"], .when(configuration: .release)),
2324
+ ${KEEP_WASM_NAMES ? '' : '.unsafeFlags(["-Xlinker", "--strip-all"], .when(configuration: .release)),'}
2297
2325
  ]`
2298
2326
  : librarySettings.length > 0
2299
2327
  ? `,
@@ -3010,8 +3038,6 @@ const FOUNDATION_OVERLAY_GLOBALS = new Set([
3010
3038
  'NSURLProtectionSpaceHTTPS',
3011
3039
  'NSURLProtectionSpaceHTTPSProxy',
3012
3040
  'NSURLProtectionSpaceSOCKSProxy',
3013
- 'NSURLSessionDownloadTaskResumeData',
3014
- 'NSURLSessionTransferSizeUnknown',
3015
3041
  'NSURLSessionUploadTaskResumeData',
3016
3042
  'NSUTF16BigEndianStringEncoding',
3017
3043
  'NSUTF16LittleEndianStringEncoding',
@@ -4610,6 +4636,7 @@ export function writeSwiftProject(
4610
4636
  // declarations are imported before swift can emit the header that native
4611
4637
  // implementations consume. give those implementations a downstream target.
4612
4638
  const importModuleMaps = new Map<string, string>()
4639
+ const sourceModuleMaps = new Map<string, { path: string; contents: string }>()
4613
4640
  const generatedModuleMaps: { output: string; module: string; published: string }[] = []
4614
4641
  for (const target of targets) {
4615
4642
  const prefix = target.path === '.' ? '' : `${target.path}/`
@@ -4938,6 +4965,10 @@ export function writeSwiftProject(
4938
4965
  stageTargetFile(target.name, path.join(layout.root, modulemap), moduleContents, {
4939
4966
  allowExisting: true,
4940
4967
  })
4968
+ sourceModuleMaps.set(swiftTargetModuleName(target.name), {
4969
+ path: path.join(layout.root, modulemap),
4970
+ contents: moduleContents.toString(),
4971
+ })
4941
4972
  // clang resolves a framework map loaded through extern module against
4942
4973
  // the map directory, not the framework root, so mirror every header
4943
4974
  // the map names there. clang looks in Headers/ and PrivateHeaders/ for
@@ -5272,6 +5303,7 @@ export function writeSwiftProject(
5272
5303
  ? ['-ivfsoverlay', headerSearch.files[0]!]
5273
5304
  : []
5274
5305
  const nativeHeaderFlags: string[] = [...headerAliasFlags]
5306
+ const nativeSwiftHeaderMaps = new Map<string, string>()
5275
5307
  const originalMaps = new Map<string, string>()
5276
5308
  for (const target of input.nativeTargets ?? []) {
5277
5309
  for (const flag of [...target.flags, ...target.cxxFlags]) {
@@ -5304,9 +5336,14 @@ export function writeSwiftProject(
5304
5336
  )
5305
5337
  const moduleName = swiftTargetModuleName(name)
5306
5338
  const originalMap = originalMaps.get(moduleName)
5307
- const originalTokens = originalMap ? moduleMapTokens(input.files[originalMap]!) : []
5339
+ const stagedMap = sourceModuleMaps.get(moduleName)
5340
+ const originalPath = originalMap
5341
+ ? path.join(layout.root, originalMap)
5342
+ : stagedMap?.path
5343
+ const originalContents = originalMap ? input.files[originalMap]! : stagedMap?.contents
5344
+ const originalTokens = originalContents ? moduleMapTokens(originalContents) : []
5308
5345
  if (
5309
- originalMap &&
5346
+ originalPath &&
5310
5347
  originalTokens.some(
5311
5348
  (token, index) =>
5312
5349
  token === 'module' &&
@@ -5314,26 +5351,26 @@ export function writeSwiftProject(
5314
5351
  originalTokens[index + 1] === `${moduleName}.Swift`),
5315
5352
  )
5316
5353
  ) {
5317
- nativeHeaderFlags.push('-fmodule-map-file=' + path.join(layout.root, originalMap))
5354
+ nativeHeaderFlags.push('-fmodule-map-file=' + originalPath)
5355
+ nativeSwiftHeaderMaps.set(name, originalPath)
5318
5356
  continue
5319
5357
  }
5320
5358
  files.set(
5321
5359
  modulemap,
5322
- originalMap
5323
- ? `extern module ${moduleName} ${JSON.stringify(path.join(layout.root, originalMap))}\nmodule ${moduleName}.Swift {\n header ${JSON.stringify(path.join(headerDirectory, name + '-Swift.h'))}\n requires objc\n}\n`
5360
+ originalPath
5361
+ ? `extern module ${moduleName} ${JSON.stringify(originalPath)}\nmodule ${moduleName}.Swift {\n header ${JSON.stringify(path.join(headerDirectory, name + '-Swift.h'))}\n requires objc\n}\n`
5324
5362
  : swiftObjCModuleMap(
5325
5363
  moduleName,
5326
- [
5327
- ...(
5328
- input.nativeTargets?.find((target) => target.name === name)
5329
- ?.publicHeaders ?? []
5330
- ).map((header) => path.join(layout.root, header)),
5331
- path.join(headerDirectory, name + '-Swift.h'),
5332
- ],
5364
+ (
5365
+ input.nativeTargets?.find((target) => target.name === name)
5366
+ ?.publicHeaders ?? []
5367
+ ).map((header) => path.join(layout.root, header)),
5333
5368
  submoduleImports.get(moduleName),
5369
+ path.join(headerDirectory, name + '-Swift.h'),
5334
5370
  ),
5335
5371
  )
5336
5372
  nativeHeaderFlags.push('-fmodule-map-file=' + modulemap)
5373
+ nativeSwiftHeaderMaps.set(name, modulemap)
5337
5374
  }
5338
5375
  // swiftpm plans a build root's build once and reuses the plan until a
5339
5376
  // manifest changes, so a file added to the module would never compile
@@ -5632,13 +5669,17 @@ export function writeSwiftProject(
5632
5669
  .replace(product, `${product}\n ${ONE_PRODUCT},`)
5633
5670
  .replace(flags, `${flags}${ONE_IMPLICIT_FLAGS}`)
5634
5671
  }
5635
- if (importsSpriteKit(input.files)) {
5672
+ const games = gameProducts(input.files)
5673
+ if (games.length > 0) {
5636
5674
  const anchor = '.product(name: "CoreLocation", package: "sootsim-swift"),'
5637
5675
  if (!manifest.includes(anchor))
5638
5676
  throw new Error(
5639
- 'scripts/contrast-app-Package.swift lost the line the SpriteKit product links at',
5677
+ 'scripts/contrast-app-Package.swift lost the line the game products link at',
5640
5678
  )
5641
- manifest = manifest.replace(anchor, `${anchor}\n ${SPRITEKIT_PRODUCT},`)
5679
+ manifest = manifest.replace(
5680
+ anchor,
5681
+ [anchor, ...games.map((name) => `${gameProduct(name)},`)].join('\n '),
5682
+ )
5642
5683
  }
5643
5684
  files.set(path.join(layout.root, 'Package.swift'), manifest + moduleFiles)
5644
5685
  } else {
@@ -5788,6 +5829,7 @@ export function writeSwiftProject(
5788
5829
  nativeFlags,
5789
5830
  nativeImportFlags,
5790
5831
  nativeHeaderFlags,
5832
+ nativeSwiftHeaderMaps,
5791
5833
  nativeHeaderDeclarations,
5792
5834
  objcImplementationModules: new Set(
5793
5835
  targets
@@ -5807,7 +5849,7 @@ export function writeSwiftProject(
5807
5849
  siblings: moduleSiblings,
5808
5850
  moduleRoot,
5809
5851
  oneModule: input.oneModule,
5810
- spriteKit: importsSpriteKit(input.files),
5852
+ gameProducts: gameProducts(input.files),
5811
5853
  distribution: distribution ?? undefined,
5812
5854
  moduleExcludes,
5813
5855
  deferDarwinOverlay:
@@ -5974,31 +6016,30 @@ export function writeSwiftProject(
5974
6016
  ),
5975
6017
  ]),
5976
6018
  )
6019
+ const producerSet = new Set([...Object.keys(nativeHeaderDeclarations), ...producers])
6020
+ const orderedProducers: string[] = []
5977
6021
  const included = new Set<string>()
5978
6022
  const include = (name: string): void => {
5979
6023
  if (included.has(name)) return
5980
6024
  included.add(name)
5981
6025
  for (const dependency of dependencies.get(name) ?? []) include(dependency)
6026
+ if (producerSet.has(name)) orderedProducers.push(name)
5982
6027
  }
5983
- for (const producer of producers)
6028
+ for (const producer of producerSet)
5984
6029
  for (const dependency of dependencies.get(producer) ?? []) include(dependency)
5985
- // building a producer also emits its dependency producers' headers.
6030
+ const needsHeaderStages = [...producerSet].some((name) => included.has(name))
6031
+ for (const producer of producerSet) include(producer)
5986
6032
  return {
5987
6033
  nativeBuildTargets: [...native].flatMap(([name, plan]) => [
5988
6034
  ...(!plan.mixed && plan.sources.length > 0 ? [name] : []),
5989
6035
  ...(plan.groups?.map((group) => group.name) ?? (plan.late ? [plan.late.name] : [])),
5990
6036
  ]),
5991
- headerProducers: [
5992
- ...new Set([
5993
- ...Object.keys(nativeHeaderDeclarations),
5994
- ...producers.filter((name) => !included.has(name)),
5995
- ]),
5996
- ],
6037
+ headerProducers: orderedProducers,
5997
6038
  emittedHeaderProducers: [
5998
6039
  ...new Set([...Object.keys(nativeHeaderDeclarations), ...producers]),
5999
6040
  ],
6000
6041
  nativeHeaderDeclarations,
6001
- headerProductTarget,
6042
+ headerProductTarget: needsHeaderStages ? undefined : headerProductTarget,
6002
6043
  targetFailures,
6003
6044
  generatedOwners,
6004
6045
  targetToolset,
@@ -6332,14 +6373,9 @@ function linkedFrameworkTargets(
6332
6373
  ))
6333
6374
  reach(match[1]!)
6334
6375
  }
6335
- if (importsSpriteKit(input.files))
6336
- for (const name of ['SpriteKit', 'PeachBox2D', 'simd', 'GLKit']) reach(name)
6337
- const wanted = [
6338
- 'PeachApp',
6339
- ...(input.oneModule ? ['One'] : []),
6340
- ...(importsSpriteKit(input.files) ? ['SpriteKit'] : []),
6341
- ...reached,
6342
- ]
6376
+ const games = gameProducts(input.files)
6377
+ for (const product of games) for (const name of GAME_REACH[product] ?? []) reach(name)
6378
+ const wanted = ['PeachApp', ...(input.oneModule ? ['One'] : []), ...games, ...reached]
6343
6379
  const linked = [...new Set(wanted)].filter((name) => libraries.has(name))
6344
6380
  if (!linked.includes('PeachApp'))
6345
6381
  throw new Error('swift-compile: the framework package declares no PeachApp library')
@@ -139,20 +139,22 @@ export function planAssetSymbols(
139
139
  // xcode 27.1 resolves an unset TARGETED_DEVICE_FAMILY to 1 for an iOS target
140
140
  // (xcodebuild -showBuildSettings on an application target that never sets it)
141
141
  const families = (settings.TARGETED_DEVICE_FAMILY || '1').split(',').sort().join(',')
142
- // families 6 and 7 change neither the devices nor symbols for the iOS slice
142
+ // families 3, 6 and 7 change neither the devices nor symbols for the iOS
143
+ // slice (3: actool output identical for 1,3,7 and 1,7, xcode 27.1)
143
144
  const devices: AssetDevice[] | undefined = (
144
145
  {
145
146
  '1': ['iphone'],
146
147
  '2': ['ipad'],
147
148
  '1,2': ['iphone', 'ipad'],
148
149
  '1,7': ['iphone'],
150
+ '1,3,7': ['iphone'],
149
151
  '1,2,6': ['iphone', 'ipad'],
150
152
  '1,2,7': ['iphone', 'ipad'],
151
153
  } satisfies Record<string, AssetDevice[]>
152
154
  )[families]
153
155
  if (!devices)
154
156
  return unknown(
155
- `Generated asset symbols are proven only for TARGETED_DEVICE_FAMILY 1, 2, 1,2, 1,7, 1,2,6 or 1,2,7; this target resolves to "${families}".`,
157
+ `Generated asset symbols are proven only for TARGETED_DEVICE_FAMILY 1, 2, 1,2, 1,7, 1,3,7, 1,2,6 or 1,2,7; this target resolves to "${families}".`,
156
158
  )
157
159
  const plan: SwiftAssetPlan = {
158
160
  catalogs: catalogs.map((path) => ({
package/src/swift-host.ts CHANGED
@@ -1773,6 +1773,33 @@ export function sourceFiles(root: string): string[] {
1773
1773
  }
1774
1774
 
1775
1775
  // project files and the xcconfigs their settings read are build inputs, never resources
1776
+ // the app target's resources Xcode copies to the bundle root: a folder
1777
+ // reference keeps its tree and a catalog compiles, so neither is flat
1778
+ function xcodeFlatResources(
1779
+ app: Pick<XcodeModuleTarget, 'resources' | 'resourceNames'> | undefined,
1780
+ ): ReadonlySet<string> {
1781
+ if (!app) return new Set()
1782
+ return new Set(
1783
+ app.resources.filter(
1784
+ (file) =>
1785
+ !Object.hasOwn(app.resourceNames, file) &&
1786
+ !file.split(path.sep).some((segment) => segment.endsWith('.xcassets')),
1787
+ ),
1788
+ )
1789
+ }
1790
+
1791
+ // an xcode target declares its resources independently of their source
1792
+ // directory; the bundle file and image enumerators use Resources as their root marker.
1793
+ function bundleResourcePath(
1794
+ flatResources: ReadonlySet<string>,
1795
+ disk: string,
1796
+ file: string,
1797
+ ): string {
1798
+ return flatResources.has(disk) && !file.split('/').includes('Resources')
1799
+ ? `Resources/${file}`
1800
+ : file
1801
+ }
1802
+
1776
1803
  function resourceCandidateFiles(root: string): string[] {
1777
1804
  return packageFiles(root, false).filter(
1778
1805
  (file) =>
@@ -1941,12 +1968,24 @@ export function buildSwiftResourceTable(
1941
1968
  const disk = byRelative.get(file)
1942
1969
  return !disk || !layout?.targetDirs.get(disk)?.startsWith(`${XCODE_FRAMEWORKS_DIR}/`)
1943
1970
  })
1944
- const found = enumerateBundleImages(relative, readContents, (file) => {
1945
- const disk = byRelative.get(file)
1946
- if (!disk) throw new Error(`image source ${file} has no input file`)
1947
- const key = layout ? xcodeInputKey(layout, disk, file) : file
1948
- return swiftResourceBundleForFile(bundles, key)?.path ?? '.'
1949
- })
1971
+ const flatResources = xcodeFlatResources(layout?.app)
1972
+ const bundled = new Map(
1973
+ relative.map((file) => [
1974
+ bundleResourcePath(flatResources, byRelative.get(file)!, file),
1975
+ file,
1976
+ ]),
1977
+ )
1978
+ const found = enumerateBundleImages(
1979
+ [...bundled.keys()],
1980
+ (file) => readContents(bundled.get(file) ?? file),
1981
+ (marked) => {
1982
+ const file = bundled.get(marked) ?? marked
1983
+ const disk = byRelative.get(file)
1984
+ if (!disk) throw new Error(`image source ${file} has no input file`)
1985
+ const key = layout ? xcodeInputKey(layout, disk, file) : file
1986
+ return swiftResourceBundleForFile(bundles, key)?.path ?? '.'
1987
+ },
1988
+ ).map((image) => ({ ...image, file: bundled.get(image.file) ?? image.file }))
1950
1989
  const colors = enumerateBundleColors(catalogFiles, readContents)
1951
1990
  const symbols = enumerateBundleSymbols(catalogFiles, readContents, (file) => {
1952
1991
  const disk = byRelative.get(file)
@@ -2180,20 +2219,20 @@ export function buildSwiftBundleFiles(
2180
2219
  }
2181
2220
  }
2182
2221
  const groups = new Map<string, Map<string, string>>()
2183
- const appResources = new Set(xcode?.app.resources ?? [])
2222
+ const flatResources = xcodeFlatResources(xcode?.app)
2184
2223
  for (const file of relative) {
2185
2224
  const disk = path.join(packagePath, ...file.split('/'))
2225
+ const folderName = xcode?.app.resourceNames[disk]
2226
+ if (folderName) {
2227
+ watched.push(file)
2228
+ addFile(folderName, readPackageFile(packagePath, disk, limits.resourceBytes))
2229
+ continue
2230
+ }
2186
2231
  const key = xcode ? xcodeInputKey(xcode, disk, file) : file
2187
2232
  const bundle = swiftResourceBundleForFile(bundles, key)
2188
2233
  const bundlePath = bundle?.path ?? '.'
2189
2234
  const group = groups.get(bundlePath) ?? new Map<string, string>()
2190
- // an xcode target declares its resources independently of their source
2191
- // directory; the bundle enumerator uses Resources as its root marker.
2192
- const resource =
2193
- appResources.has(disk) && !file.split('/').includes('Resources')
2194
- ? `Resources/${file}`
2195
- : file
2196
- group.set(resource, disk)
2235
+ group.set(bundleResourcePath(flatResources, disk, file), disk)
2197
2236
  groups.set(bundlePath, group)
2198
2237
  }
2199
2238
  for (const bundle of bundles) {
@@ -49,6 +49,7 @@ export function swiftObjCModuleMap(
49
49
  module: string,
50
50
  headers: readonly string[],
51
51
  submodules: ReadonlySet<string> = new Set(),
52
+ swiftHeader?: string,
52
53
  ): string {
53
54
  const header = (file: string) => `header ${JSON.stringify(file)}`
54
55
  const stem = (file: string) =>
@@ -60,6 +61,9 @@ export function swiftObjCModuleMap(
60
61
  ? ` module ${stem(file)} { ${header(file)} export * }`
61
62
  : ` ${header(file)}`,
62
63
  ),
64
+ ...(swiftHeader
65
+ ? [' module Swift {', ` ${header(swiftHeader)}`, ' requires objc', ' }']
66
+ : []),
63
67
  ' export *',
64
68
  '}',
65
69
  '',