peachsim 0.1.1635 → 0.1.1641

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 (142) hide show
  1. package/README.md +10 -1
  2. package/cli/app-config.ts +14 -5
  3. package/cli/app-stubs.ts +96 -22
  4. package/cli/bridge-flow-runner.ts +2 -2
  5. package/cli/cloud-client.ts +60 -40
  6. package/cli/cloud-session.ts +16 -0
  7. package/cli/commands/assert.ts +17 -4
  8. package/cli/commands/box.ts +33 -19
  9. package/cli/commands/doctor.ts +37 -20
  10. package/cli/commands/flow.ts +1 -1
  11. package/cli/commands/inspect/actions.ts +3 -2
  12. package/cli/commands/inspect/core.ts +0 -8
  13. package/cli/commands/inspect/shared.ts +9 -4
  14. package/cli/commands/inspect.ts +65 -61
  15. package/cli/commands/platform.ts +2 -1
  16. package/cli/commands/preview.ts +1 -1
  17. package/cli/commands/record.ts +154 -68
  18. package/cli/commands/swift.ts +18 -4
  19. package/cli/commands/three-mode.ts +4 -38
  20. package/cli/commands/upload.ts +55 -49
  21. package/cli/drivers/playwright-sim-host.ts +65 -42
  22. package/cli/flow-live-status.ts +0 -2
  23. package/cli/main.ts +2 -2
  24. package/cli/outbound-endpoints.ts +7 -5
  25. package/cli/pause-aware-timeout.ts +4 -0
  26. package/cli/ws-bridge.ts +19 -16
  27. package/detox/attempt-events.cjs +99 -6
  28. package/detox/attempt-events.d.cts +7 -0
  29. package/detox/expectations.ts +7 -3
  30. package/detox/index.ts +259 -139
  31. package/detox/jest-environment.cjs +5 -1
  32. package/detox/motion-change.cjs +170 -21
  33. package/detox/motion-change.d.cts +32 -0
  34. package/detox/navigation-lifecycle.ts +35 -3
  35. package/dist-lib/agent-daemon-client.cjs +1 -1
  36. package/dist-lib/agent-events.cjs +1 -1
  37. package/dist-lib/attached-projects.cjs +1 -1
  38. package/dist-lib/auth/shared-session.cjs +1 -1
  39. package/dist-lib/backend-origin.cjs +17 -7
  40. package/dist-lib/beta.cjs +1 -1
  41. package/dist-lib/beta.mjs +1 -1
  42. package/dist-lib/bridge-constants.cjs +1 -1
  43. package/dist-lib/bridge-contract-input.cjs +1 -1
  44. package/dist-lib/bridge-contract-input.mjs +1 -1
  45. package/dist-lib/bridge-contract.cjs +1 -1
  46. package/dist-lib/bridge-contract.mjs +1 -1
  47. package/dist-lib/bundle-channel.cjs +5014 -2268
  48. package/dist-lib/capture-contract.cjs +1 -1
  49. package/dist-lib/capture-contract.mjs +1 -1
  50. package/dist-lib/cloud-contract.cjs +1 -1
  51. package/dist-lib/cloud-contract.mjs +1 -1
  52. package/dist-lib/cloud-test-args.cjs +1 -1
  53. package/dist-lib/cloud-test-args.mjs +1 -1
  54. package/dist-lib/cloud.cjs +1 -1
  55. package/dist-lib/cloud.mjs +1 -1
  56. package/dist-lib/config.cjs +1 -1
  57. package/dist-lib/config.mjs +1 -1
  58. package/dist-lib/detox/index.cjs +1036 -166
  59. package/dist-lib/detox/jest-environment.cjs +5 -1
  60. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  61. package/dist-lib/expo-swift-bundle.cjs +4613 -1963
  62. package/dist-lib/expo-swift-metadata.cjs +9 -7
  63. package/dist-lib/expo-swift-metadata.mjs +9 -7
  64. package/dist-lib/home-paths.cjs +1 -1
  65. package/dist-lib/host/bridge-host.cjs +58072 -16400
  66. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  67. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  68. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  69. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  70. package/dist-lib/host/websocket-proxy.cjs +1 -1
  71. package/dist-lib/index.cjs +2790 -143
  72. package/dist-lib/jump-to-source-babel.cjs +1 -1
  73. package/dist-lib/jump-to-source-native.cjs +1 -1
  74. package/dist-lib/kotlin-host.cjs +1 -1
  75. package/dist-lib/menu.cjs +1 -1
  76. package/dist-lib/menu.mjs +1 -1
  77. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  78. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  79. package/dist-lib/metro-production-bundle.cjs +1 -1
  80. package/dist-lib/metro-production-bundle.mjs +1 -1
  81. package/dist-lib/metro.cjs +2579 -95
  82. package/dist-lib/native-app-identity-project.cjs +243 -53
  83. package/dist-lib/native-app-identity-project.mjs +243 -53
  84. package/dist-lib/native-app-identity.cjs +29 -30
  85. package/dist-lib/native-app-identity.mjs +29 -30
  86. package/dist-lib/native-firebase-metadata.cjs +1 -1
  87. package/dist-lib/native-firebase-metadata.mjs +1 -1
  88. package/dist-lib/plist.cjs +1 -1
  89. package/dist-lib/plist.mjs +1 -1
  90. package/dist-lib/profiles.cjs +1 -1
  91. package/dist-lib/public-brand.cjs +1 -1
  92. package/dist-lib/react-native-host-modules.cjs +2 -2
  93. package/dist-lib/react-native-host-modules.mjs +2 -2
  94. package/dist-lib/scripts/dev-server-scanner.cjs +8 -6
  95. package/dist-lib/sdk.cjs +4 -3
  96. package/dist-lib/sdk.mjs +4 -3
  97. package/dist-lib/skills.cjs +66475 -23097
  98. package/dist-lib/swift-compile.cjs +452 -260
  99. package/dist-lib/swift-compile.mjs +451 -252
  100. package/dist-lib/swift-package.cjs +1 -1
  101. package/dist-lib/swift-package.mjs +1 -1
  102. package/dist-lib/swift-submit.cjs +14 -7
  103. package/dist-lib/swift-submit.mjs +14 -7
  104. package/dist-lib/swift-toolchain.cjs +316 -41
  105. package/dist-lib/swift.cjs +1 -1
  106. package/dist-lib/vite-swift.cjs +8188 -5636
  107. package/dist-lib/vite.cjs +2857 -12
  108. package/dist-lib/worklet-executable.cjs +14 -2
  109. package/dist-lib/worklet-executable.mjs +14 -2
  110. package/package.json +1 -1
  111. package/src/backend-origin.ts +21 -6
  112. package/src/expo-swift-host.ts +5 -2
  113. package/src/expoKotlinContract.ts +4 -7
  114. package/src/expoSwiftContract.ts +4 -6
  115. package/src/host/staged-host-files.ts +9 -0
  116. package/src/native-app-identity-project.ts +1 -4
  117. package/src/native-app-identity.ts +1 -4
  118. package/src/nativeAppIconContract.ts +1 -4
  119. package/src/output-lock-stdio.ts +36 -0
  120. package/src/react-native-bridge-modules.ts +106 -78
  121. package/src/react-native-host-modules.ts +4 -1
  122. package/src/settings-bundle.ts +1 -4
  123. package/src/swift-compile.ts +224 -139
  124. package/src/swift-distribution.lock.json +23 -12
  125. package/src/swift-distribution.ts +103 -4
  126. package/src/swift-host.ts +95 -30
  127. package/src/swift-icu.ts +16 -10
  128. package/src/swift-objc-compile.ts +547 -0
  129. package/src/swift-objc-metadata.ts +204 -0
  130. package/src/swift-objc-sdk.ts +883 -19
  131. package/src/swift-objc-standin.ts +126 -14
  132. package/src/swift-package-manifest.ts +42 -1
  133. package/src/swift-packages.ts +35 -21
  134. package/src/swift-root-lock.ts +153 -0
  135. package/src/swift-standin-emit.ts +40 -7
  136. package/src/swift-standins.ts +404 -138
  137. package/src/swift-submit.ts +18 -7
  138. package/src/swift-toolchain.ts +378 -46
  139. package/src/swiftContract.ts +12 -0
  140. package/src/worker-system-chunks.ts +11 -12
  141. package/src/worklet-executable.ts +19 -1
  142. package/src/xcode-project.ts +280 -33
@@ -5,6 +5,7 @@
5
5
  // build carries, and unpacks it under the peach home
6
6
  // (docs/swift-distribution.md). no path here asks Contrast to compile.
7
7
  import fs from 'node:fs'
8
+ import os from 'node:os'
8
9
  import path from 'node:path'
9
10
  import { installVerifiedTarball } from '@contrast/runtime-delivery'
10
11
  import { rnxHomeDir } from './home-paths'
@@ -16,7 +17,17 @@ import {
16
17
  swiftDistributionKey,
17
18
  } from './swift-distribution-contract'
18
19
  import lockJson from './swift-distribution.lock.json' with { type: 'json' }
19
- import { swiftToolchain } from './swift-toolchain'
20
+ import {
21
+ confineSwiftScratch,
22
+ installSwiftWasmSdk,
23
+ swiftCompiler,
24
+ swiftCompilerInstallSteps,
25
+ swiftHostCompilerArchive,
26
+ swiftToolchain,
27
+ swiftToolchainBin,
28
+ verifySwiftHostCompiler,
29
+ type SwiftWasmSdk,
30
+ } from './swift-toolchain'
20
31
 
21
32
  export const SWIFT_ENDPOINT_ENV = 'PEACH_SWIFT_ENDPOINT'
22
33
 
@@ -51,9 +62,9 @@ export async function swiftDistributionRoot(
51
62
  `peach has no Swift distribution for ${process.platform}-${process.arch}; it ships for macOS arm64 and Linux x64 and arm64`,
52
63
  )
53
64
  }
54
- // the archive is useless without its compiler, so a missing or wrong
55
- // toolchain fails before the download
56
- swiftToolchain(lock.release, lock)
65
+ // the archive is useless without its compiler, so the toolchain installs
66
+ // or fails before the download
67
+ await installSwiftToolchain(lock.release, lock, signal)
57
68
  const dir = path.join(rnxHomeDir(), 'swift', `${lock.id}-${host}`)
58
69
  let pending = installs.get(dir)
59
70
  if (pending?.controller.signal.aborted) pending = undefined
@@ -126,6 +137,93 @@ export async function swiftDistributionRoot(
126
137
  }
127
138
  }
128
139
 
140
+ /**
141
+ * the pinned compiler and wasm SDK, installed on first use without root: the
142
+ * compiler under PEACH_HOME/swift/toolchains (macOS: ~/Library/Developer/Toolchains) unless CONTRAST_SWIFT_TOOLCHAINS
143
+ * or an existing /opt/swift install selects another (swiftToolchainBin), the
144
+ * SDK in the --swift-sdks-path directory, PEACH_HOME/swift/sdks by default.
145
+ * a compiler in a directory the user selected is verified, never replaced.
146
+ */
147
+ async function installSwiftToolchain(
148
+ release: string,
149
+ sdk: SwiftWasmSdk,
150
+ signal?: AbortSignal,
151
+ ): Promise<void> {
152
+ process.env.PEACH_SWIFT_SDKS_PATH ??= path.join(rnxHomeDir(), 'swift', 'sdks')
153
+ if (swiftToolchainReady(release, sdk)) return
154
+ const root = path.resolve(swiftToolchainBin(release), '../..')
155
+ // macOS installs where the locator looks first, the user's Toolchains
156
+ // directory, and only into a directory without a compiler; linux installs
157
+ // into the Peach-owned directory.
158
+ const owned =
159
+ process.platform === 'darwin'
160
+ ? path.join(os.homedir(), 'Library/Developer/Toolchains', `${release}.xctoolchain`)
161
+ : path.join(rnxHomeDir(), 'swift', 'toolchains', release)
162
+ const selectedCompiler =
163
+ process.platform === 'darwin' && fs.existsSync(path.join(root, 'usr/bin/swift'))
164
+ if (
165
+ release.includes('-custom-') &&
166
+ root === owned &&
167
+ !selectedCompiler &&
168
+ !swiftCompilerReady(release)
169
+ ) {
170
+ const archive = swiftHostCompilerArchive(release)
171
+ process.stderr.write(`peach: installing Swift compiler ${release} to ${root}…\n`)
172
+ await installVerifiedTarball({
173
+ signal,
174
+ url: archive.url,
175
+ digest: { algorithm: 'sha256', encoding: 'hex', value: archive.checksum },
176
+ destDir: root,
177
+ installedMarker: 'compiler.json',
178
+ stripComponents: 1,
179
+ label: `Swift compiler ${release}`,
180
+ verify: (dir) => verifySwiftHostCompiler(release, dir),
181
+ })
182
+ }
183
+ try {
184
+ swiftCompiler(release)
185
+ } catch (error) {
186
+ const reason = error instanceof Error ? error.message : String(error)
187
+ throw new Error(
188
+ `Swift compiler ${release} is unavailable or differs from its pin: ${reason}\n${swiftCompilerInstallSteps(release)}\nthen run the command again; it installs the SDK.`,
189
+ { cause: error },
190
+ )
191
+ }
192
+ if (swiftToolchainReady(release, sdk)) return
193
+ signal?.throwIfAborted()
194
+ const directory = process.env.PEACH_SWIFT_SDKS_PATH
195
+ process.stderr.write(`peach: installing Swift SDK ${sdk.wasmSdkId} to ${directory}…\n`)
196
+ try {
197
+ installSwiftWasmSdk(release, sdk)
198
+ } catch (error) {
199
+ const reason = error instanceof Error ? error.message : String(error)
200
+ throw new Error(
201
+ `could not install Swift SDK ${sdk.wasmSdkId} to ${directory}: ${reason}`,
202
+ {
203
+ cause: error,
204
+ },
205
+ )
206
+ }
207
+ }
208
+
209
+ function swiftCompilerReady(release: string): boolean {
210
+ try {
211
+ swiftCompiler(release)
212
+ return true
213
+ } catch {
214
+ return false
215
+ }
216
+ }
217
+
218
+ function swiftToolchainReady(release: string, sdk: SwiftWasmSdk): boolean {
219
+ try {
220
+ swiftToolchain(release, sdk)
221
+ return true
222
+ } catch {
223
+ return false
224
+ }
225
+ }
226
+
129
227
  /** the module this process compiles app Swift against: the checkout beside a
130
228
  * source peach, else the distribution, else null. `required` callers compile
131
229
  * nothing without it, so a checkout's toolchain error surfaces to them. */
@@ -134,6 +232,7 @@ export async function resolveSwiftModule({
134
232
  signal,
135
233
  }: { required?: boolean; signal?: AbortSignal } = {}): Promise<SwiftModule | null> {
136
234
  signal?.throwIfAborted()
235
+ confineSwiftScratch(path.join(rnxHomeDir(), 'cache', 'swift-tmp'))
137
236
  const checkout = path.resolve(import.meta.dirname, '../../sootsim-swift')
138
237
  if (fs.existsSync(path.join(checkout, 'toolchain.json'))) {
139
238
  // a checkout is implicit: without its toolchain, apps run without their
package/src/swift-host.ts CHANGED
@@ -15,6 +15,8 @@ import { authHeaderValue, resolveCliAuth } from '../cli/auth'
15
15
  import { resolveContrastBackendOrigin } from './backend-origin'
16
16
  import { cacheDir } from './home-paths'
17
17
  import iosSdk from './ios-sdk-frameworks.json' with { type: 'json' }
18
+ import { type NativeAppIdentity } from './native-app-identity'
19
+ import { readNativeAppIdentityFromXcodeTarget } from './native-app-identity-project'
18
20
  import { swiftBinaryModules } from './swift-binary-modules'
19
21
  import {
20
22
  compileSwiftProjectUnit,
@@ -28,6 +30,7 @@ import {
28
30
  } from './swift-compile'
29
31
  import { designTimeValues } from './swift-design-time'
30
32
  import { swiftDistributionRoot, swiftModuleToolchain } from './swift-distribution'
33
+ import { compileXcodeObjCImplementations } from './swift-objc-compile'
31
34
  import {
32
35
  swiftImplicitResourcePaths,
33
36
  swiftMetalSourceFiles,
@@ -267,7 +270,9 @@ export function openXcodeProject(
267
270
  moduleRoot: string | undefined,
268
271
  configuration?: string,
269
272
  ): string {
270
- const app = readXcodeAppTarget(xcodeproj, target, configuration)
273
+ const app = readXcodeAppTarget(xcodeproj, target, configuration, {
274
+ prepareGeneratedSources: true,
275
+ })
271
276
  const root = app.rootDir
272
277
  xcodeProjects.set(root, {
273
278
  xcodeproj,
@@ -312,20 +317,20 @@ function swiftPackages(
312
317
  projectGaps: Map<string, string>
313
318
  binaries: SwiftPackageBinary[]
314
319
  } {
315
- const stamp = (file: string | null) =>
316
- file && fs.existsSync(file) ? fs.statSync(file).mtimeMs : 0
320
+ const stamp = (file: string) =>
321
+ fs.existsSync(file)
322
+ ? createHash('sha256').update(fs.readFileSync(file)).digest('hex')
323
+ : null
317
324
  const key = JSON.stringify([
318
325
  app.linkedProducts,
319
- app.packageResolved,
320
- stamp(app.packageResolved),
326
+ app.packageResolved.map((file) => [file, stamp(file)]),
321
327
  app.localPackageDirs.map((dir) => stamp(path.join(dir, 'Package.swift'))),
322
328
  app.linkedProducts.map(({ source }) =>
323
329
  source.kind === 'local' ? stamp(path.join(source.dir, 'Package.swift')) : 0,
324
330
  ),
325
331
  ])
326
- // the package key is cheap stamps; the stand-in key covers the generation
327
- // inputs the stamps cannot see (artifact contents, peach's modules, the
328
- // emitter), so a changed input rebuilds instead of reusing stale output.
332
+ // resolution follows lock and manifest contents even when a checkout restores
333
+ // timestamps. the stand-in key also covers artifacts, peach modules and emitter.
329
334
  let cached = entry.packages
330
335
  if (cached?.key !== key) {
331
336
  const packages = resolveSwiftPackages({
@@ -345,6 +350,13 @@ function swiftPackages(
345
350
  }
346
351
  entry.packages = cached
347
352
  }
353
+ if (
354
+ entry.moduleRoot &&
355
+ [app, ...app.frameworks].some((target) =>
356
+ target.allSources.some((file) => /\.mm?$/.test(file)),
357
+ )
358
+ )
359
+ compileXcodeObjCImplementations(app, cached.binaries, entry.moduleRoot)
348
360
  // a reached binary target with stand-ins for every module it ships
349
361
  // resolves as provided, so what depends on it compiles; the rest keep
350
362
  // their reason with why no stand-in was built. surfaces peach already
@@ -450,11 +462,26 @@ function xcodeLayout(root: string): XcodeLayout | null {
450
462
  registered.xcodeproj,
451
463
  registered.target,
452
464
  registered.configuration,
465
+ { prepareGeneratedSources: true },
453
466
  )
467
+ const implementations = [app, ...app.frameworks].flatMap((target) =>
468
+ target.allSources
469
+ .filter((file) => /\.mm?$/i.test(file))
470
+ .map((file) => `${target.name}: ${path.relative(app.rootDir, file)}`),
471
+ )
472
+ if (implementations.length > 0 && !registered.moduleRoot)
473
+ throw new Error(
474
+ `Peach cannot compile Objective-C implementations in Xcode targets until the runtime and shared Swift object bridge are available:\n${implementations.map((file) => ` ${file}`).join('\n')}`,
475
+ )
454
476
  const appSources = app.allSources.filter(
455
477
  (file) => file.endsWith('.swift') || file.endsWith('.metal'),
456
478
  )
457
- const files = new Set([...app.projectFiles, ...appSources, ...app.resources])
479
+ const files = new Set([
480
+ ...app.projectFiles,
481
+ ...app.packageResolved,
482
+ ...appSources,
483
+ ...app.resources,
484
+ ])
458
485
  if (app.infoPlistFile) files.add(app.infoPlistFile)
459
486
  const targetDirs = new Map(
460
487
  [...appSources, ...app.resources].map((file) => [file, XCODE_APP_DIR]),
@@ -605,10 +632,18 @@ function xcodeManifest(layout: XcodeLayout): string {
605
632
 
606
633
  // preflight the entire input tree before reading any source or resource bytes.
607
634
  // a nested package is a separate compile unit; ignored build trees never enter it.
608
- function packageFiles(root: string): string[] {
635
+ function packageFiles(root: string, includeBuildInputs = true): string[] {
609
636
  const canonicalRoot = fs.realpathSync(root)
610
637
  const xcode = xcodeLayout(canonicalRoot)
611
- return xcode ? xcode.files : sourceTreeFiles(canonicalRoot, canonicalRoot)
638
+ if (!xcode) return sourceTreeFiles(canonicalRoot, canonicalRoot)
639
+ if (includeBuildInputs) return xcode.files
640
+ const buildInputs = new Set(xcode.app.projectFiles)
641
+ return xcode.files.filter(
642
+ (file) =>
643
+ !buildInputs.has(file) ||
644
+ xcode.targetDirs.has(file) ||
645
+ xcode.packages.targets.some((target) => target.files.includes(file)),
646
+ )
612
647
  }
613
648
 
614
649
  // remote dependencies are enumerated from peach's checkout cache, while app
@@ -668,16 +703,18 @@ function isPackageSource(file: string): boolean {
668
703
  }
669
704
 
670
705
  export function sourceFiles(root: string): string[] {
671
- return packageFiles(root).filter(isPackageSource)
706
+ return packageFiles(root, false).filter(isPackageSource)
672
707
  }
673
708
 
674
709
  // project files and the xcconfigs their settings read are build inputs, never resources
675
710
  function resourceCandidateFiles(root: string): string[] {
676
- return packageFiles(root).filter(
711
+ return packageFiles(root, false).filter(
677
712
  (file) =>
678
713
  !isPackageSource(file) &&
679
714
  !file.endsWith('.xcconfig') &&
680
- !['project.pbxproj', 'project.xcproj'].includes(path.basename(file)),
715
+ !['project.pbxproj', 'project.xcproj', 'Package.resolved'].includes(
716
+ path.basename(file),
717
+ ),
681
718
  )
682
719
  }
683
720
 
@@ -685,6 +722,7 @@ function resourceCandidateFiles(root: string): string[] {
685
722
  export function swiftProjectAppInfo(packagePath: string): {
686
723
  name: string
687
724
  icon: { bytes: Buffer; contentType: string } | null
725
+ identity: NativeAppIdentity | null
688
726
  } {
689
727
  const registered = xcodeProjects.get(packagePath)
690
728
  const app = registered
@@ -747,12 +785,17 @@ export function swiftProjectAppInfo(packagePath: string): {
747
785
  }
748
786
  break
749
787
  }
750
- return { name: app?.name ?? path.basename(packagePath), icon }
788
+ return {
789
+ name: app?.name ?? path.basename(packagePath),
790
+ icon,
791
+ identity: app ? readNativeAppIdentityFromXcodeTarget(app) : null,
792
+ }
751
793
  }
752
794
 
753
795
  // the hosted compile service caps what a project sends it; a local compile
754
796
  // reads the author's own files, so nothing caps them there.
755
797
  const HOSTED_LIMITS = {
798
+ sourceBytes: MAX_SWIFT_COMPILE_SOURCE_BYTES,
756
799
  resources: MAX_SWIFT_COMPILE_RESOURCES,
757
800
  resourceBytes: MAX_SWIFT_COMPILE_RESOURCE_BYTES,
758
801
  resourceTotalBytes: MAX_SWIFT_COMPILE_RESOURCE_TOTAL_BYTES,
@@ -761,6 +804,7 @@ const HOSTED_LIMITS = {
761
804
  imageTotalBytes: MAX_SWIFT_COMPILE_IMAGE_TOTAL_BYTES,
762
805
  }
763
806
  const LOCAL_LIMITS: typeof HOSTED_LIMITS = {
807
+ sourceBytes: Infinity,
764
808
  resources: Infinity,
765
809
  resourceBytes: Infinity,
766
810
  resourceTotalBytes: Infinity,
@@ -969,7 +1013,7 @@ export function buildSwiftBundleFiles(
969
1013
  const xcode = xcodeLayout(packagePath)
970
1014
  const bundles =
971
1015
  xcode?.resourceBundles ??
972
- swiftResourceBundles(inputs ?? packageSources(packagePath), relative)
1016
+ swiftResourceBundles(inputs ?? packageSources(packagePath, hosted), relative)
973
1017
  const addFile = (key: string, bytes: Buffer) => {
974
1018
  if (
975
1019
  Object.keys(files).length >= limits.resources ||
@@ -1175,6 +1219,7 @@ export function standinLibraries(
1175
1219
 
1176
1220
  function packageSources(
1177
1221
  packagePath: string,
1222
+ hosted = false,
1178
1223
  linked: ReadonlySet<SwiftStandin> = new Set(),
1179
1224
  ): Record<string, string> {
1180
1225
  const xcode = xcodeLayout(packagePath)
@@ -1184,13 +1229,14 @@ function packageSources(
1184
1229
  ),
1185
1230
  )
1186
1231
  const files: Record<string, string> = {}
1232
+ const limits = inputLimits(hosted)
1187
1233
  for (const file of sourceFiles(packagePath)) {
1188
1234
  const relative = path.relative(packagePath, file).split(path.sep).join(path.posix.sep)
1189
1235
  if (manifests.has(file)) continue
1190
1236
  files[xcode ? xcodeInputKey(xcode, file, relative) : relative] = readPackageFile(
1191
1237
  packagePath,
1192
1238
  file,
1193
- MAX_SWIFT_COMPILE_SOURCE_BYTES,
1239
+ limits.sourceBytes,
1194
1240
  ).toString('utf8')
1195
1241
  }
1196
1242
  if (xcode) {
@@ -1221,7 +1267,11 @@ function packageSnapshot(project: SwiftProject, entry: string): SwiftPackageSnap
1221
1267
  const linked = layout
1222
1268
  ? standinLibraries(layout.binaries, layout.standins, layout.app.rootDir)
1223
1269
  : new Map<string, SwiftStandin[]>()
1224
- const files = packageSources(project.packagePath, new Set([...linked.values()].flat()))
1270
+ const files = packageSources(
1271
+ project.packagePath,
1272
+ Boolean(project.hosted),
1273
+ new Set([...linked.values()].flat()),
1274
+ )
1225
1275
  const native = nativeSourceDescriptor(project.packagePath)
1226
1276
  return {
1227
1277
  files,
@@ -1262,7 +1312,7 @@ function packageSnapshot(project: SwiftProject, entry: string): SwiftPackageSnap
1262
1312
  glue: readPackageFile(
1263
1313
  project.packagePath,
1264
1314
  path.join(project.packagePath, NATIVE_GLUE),
1265
- MAX_SWIFT_COMPILE_SOURCE_BYTES,
1315
+ inputLimits(Boolean(project.hosted)).sourceBytes,
1266
1316
  ).toString('utf8'),
1267
1317
  }
1268
1318
  : {}),
@@ -1351,10 +1401,11 @@ async function compile(
1351
1401
  throw new SwiftMissingModulesError(await missingModulesMessage(packagePath, missing))
1352
1402
  const artifact = await compileSwiftProjectUnit(input, {
1353
1403
  // the compile rewrites Sources/App in its root before swiftpm builds, and
1354
- // its lock orders one process only: two vites on one root (a lane's dev
1355
- // server and a capture, or two checkouts) build each other's sources. so
1356
- // each app and module checkout gets its own root, which keeps the same
1357
- // app's incremental build across restarts.
1404
+ // a root lock orders every process using it, so two vites on one root
1405
+ // (a lane's dev server and a capture, or two checkouts) queue instead of
1406
+ // building each other's sources. each app and module checkout still gets
1407
+ // its own root, so they do not queue and the same app keeps its
1408
+ // incremental build across restarts.
1358
1409
  buildRoot: (() => {
1359
1410
  if (process.env.CONTRAST_SWIFT_BUILD_ROOT)
1360
1411
  return process.env.CONTRAST_SWIFT_BUILD_ROOT
@@ -1411,6 +1462,21 @@ const API_GAP_PATTERNS: [RegExp, (match: RegExpExecArray) => string][] = [
1411
1462
  (match) => `${match[1]}.${match[2]}`,
1412
1463
  ],
1413
1464
  [/^'([^']+)' is unavailable/, (match) => match[1]!],
1465
+ // an instance api Peach declares static, or the reverse
1466
+ [
1467
+ /^(?:static|instance) member '([^']+)' cannot be used on (?:instance of )?type '([^']+)'/,
1468
+ (match) => `${match[2]}.${match[1]}`,
1469
+ ],
1470
+ // an overridable api Peach declares in an extension
1471
+ [
1472
+ /^(?:non-@objc )?[\w ]+ '([^']+)' is declared in extension of '([^']+)' and cannot be overridden/,
1473
+ (match) => `${match[2]}.${match[1]}`,
1474
+ ],
1475
+ // a form of a macro Peach's expansion does not accept
1476
+ [
1477
+ /^(@\w+) requires an initial value/,
1478
+ (match) => `${match[1]} without an initial value`,
1479
+ ],
1414
1480
  [/^no such module '([^']+)'/, (match) => `module ${match[1]}`],
1415
1481
  ]
1416
1482
 
@@ -1425,7 +1491,7 @@ export function swiftApiGapReport(output: string): string | null {
1425
1491
  const file = diagnostic[1]!
1426
1492
  .replace(new RegExp(`^(?:${XCODE_APP_DIR}|${XCODE_FRAMEWORKS_DIR}/[^/]+)/`), '')
1427
1493
  .replace(new RegExp(`^${XCODE_PACKAGES_DIR}/`), '')
1428
- const message = diagnostic[4]!
1494
+ const message = diagnostic[4]!.trim()
1429
1495
  const rule = API_GAP_PATTERNS.find(([pattern]) => pattern.test(message))
1430
1496
  const key = rule ? rule[1](rule[0].exec(message)!) : message
1431
1497
  const table = rule ? gaps : other
@@ -1591,6 +1657,7 @@ async function compileRemote(
1591
1657
  },
1592
1658
  origin,
1593
1659
  signal,
1660
+ (stage) => console.error(`Peach: compiling ${path.basename(packagePath)}: ${stage}`),
1594
1661
  )
1595
1662
  // the app's JS calls with the hash its own `one typegen` wrote; glue the
1596
1663
  // service rendered from another One would reject every call as stale.
@@ -1726,9 +1793,8 @@ export function swiftPackageWatchFiles(
1726
1793
  packagePath = fs.realpathSync(packagePath)
1727
1794
  return [
1728
1795
  ...(xcodeProjects.has(packagePath) ? [] : [path.join(packagePath, 'Package.swift')]),
1729
- ...sourceFiles(packagePath),
1796
+ ...packageFiles(packagePath),
1730
1797
  ...nativeSourceFiles(packagePath),
1731
- ...resourceCandidateFiles(packagePath),
1732
1798
  ...(moduleRoot
1733
1799
  ? swiftModuleInputFiles(moduleRoot).map((file) => path.join(moduleRoot, file))
1734
1800
  : []),
@@ -1744,14 +1810,14 @@ export async function swiftPackageMetadata(
1744
1810
  signal?: AbortSignal,
1745
1811
  ): Promise<SwiftEntryModuleArgs> {
1746
1812
  packagePath = fs.realpathSync(packagePath)
1813
+ const project = projectFor(packagePath, hosted, signal)
1814
+ const limits = inputLimits(Boolean(project.hosted))
1747
1815
  const mains = sourceFiles(packagePath).filter(
1748
1816
  (file) =>
1749
1817
  file.endsWith('.swift') &&
1750
1818
  swiftApplicationEntry(
1751
1819
  file,
1752
- readPackageFile(packagePath, file, MAX_SWIFT_COMPILE_SOURCE_BYTES).toString(
1753
- 'utf8',
1754
- ),
1820
+ readPackageFile(packagePath, file, limits.sourceBytes).toString('utf8'),
1755
1821
  ),
1756
1822
  )
1757
1823
  const native = nativeSourceDescriptor(packagePath)
@@ -1760,7 +1826,6 @@ export async function swiftPackageMetadata(
1760
1826
  `swift package ${packageName} needs exactly one application entry source or generated native modules; found ${mains.length}`,
1761
1827
  )
1762
1828
  }
1763
- const project = projectFor(packagePath, hosted, signal)
1764
1829
  const entry =
1765
1830
  mains.length === 1
1766
1831
  ? path.relative(packagePath, mains[0]).split(path.sep).join(path.posix.sep)
package/src/swift-icu.ts CHANGED
@@ -4,7 +4,7 @@ import { existsSync, readFileSync } from 'node:fs'
4
4
  import path from 'node:path'
5
5
  import { promisify } from 'node:util'
6
6
  import { gzip } from 'node:zlib'
7
- import { swiftSdkOptions } from './swift-toolchain.ts'
7
+ import { swiftEnvironment, swiftSdkOptions } from './swift-toolchain.ts'
8
8
 
9
9
  /**
10
10
  * the icu common data a swift artifact reserves and its host fills
@@ -73,17 +73,23 @@ async function load(toolchainBin: string, sdk: string): Promise<Buffer> {
73
73
  // missing piece instead of shelling out to sdk configure.
74
74
  if (!existsSync(swift))
75
75
  throw new Error(`swift-icu: no swift at ${swift} for the ${sdk} SDK's icu data`)
76
- const { stdout: listed } = await run(swift, ['sdk', 'list', ...swiftSdkOptions()])
76
+ const { stdout: listed } = await run(swift, ['sdk', 'list', ...swiftSdkOptions()], {
77
+ env: swiftEnvironment(),
78
+ })
77
79
  if (!listed.split('\n').some((line) => line.trim() === sdk))
78
80
  throw new Error(`swift-icu: ${sdk} is not installed for ${swift}`)
79
- const { stdout } = await run(swift, [
80
- 'sdk',
81
- 'configure',
82
- ...swiftSdkOptions(),
83
- '--show-configuration',
84
- sdk,
85
- 'wasm32-unknown-wasip1',
86
- ])
81
+ const { stdout } = await run(
82
+ swift,
83
+ [
84
+ 'sdk',
85
+ 'configure',
86
+ ...swiftSdkOptions(),
87
+ '--show-configuration',
88
+ sdk,
89
+ 'wasm32-unknown-wasip1',
90
+ ],
91
+ { env: swiftEnvironment() },
92
+ )
87
93
  const resources = /^swiftStaticResourcesPath: (.+)$/m.exec(stdout)?.[1]?.trim()
88
94
  if (!resources) throw new Error(`swift-icu: ${sdk} names no static resources`)
89
95
  // a copy, so the 42 MB archive it was sliced from can be collected