peachsim 0.1.2209 → 0.1.2213

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 (141) hide show
  1. package/cli/command-registry.ts +1 -0
  2. package/cli/commands/inspect/settling.ts +1 -1
  3. package/cli/commands/map.ts +576 -0
  4. package/cli/commands/runtime.ts +1 -1
  5. package/cli/main.ts +6 -0
  6. package/cli/outbound-endpoints.ts +11 -0
  7. package/dist-lib/agent-daemon-client.cjs +1 -1
  8. package/dist-lib/agent-events.cjs +1 -1
  9. package/dist-lib/attached-projects.cjs +1 -1
  10. package/dist-lib/auth/shared-session.cjs +1 -1
  11. package/dist-lib/backend-origin.cjs +1 -1
  12. package/dist-lib/beta.cjs +1 -1
  13. package/dist-lib/beta.mjs +1 -1
  14. package/dist-lib/bridge-constants.cjs +1 -1
  15. package/dist-lib/bridge-contract-input.cjs +1 -1
  16. package/dist-lib/bridge-contract-input.mjs +1 -1
  17. package/dist-lib/bridge-contract.cjs +1 -1
  18. package/dist-lib/bridge-contract.mjs +1 -1
  19. package/dist-lib/bundle-channel.cjs +282 -53
  20. package/dist-lib/capture-contract.cjs +1 -1
  21. package/dist-lib/capture-contract.mjs +1 -1
  22. package/dist-lib/cloud-contract.cjs +1 -1
  23. package/dist-lib/cloud-contract.mjs +1 -1
  24. package/dist-lib/cloud-test-args.cjs +1 -1
  25. package/dist-lib/cloud-test-args.mjs +1 -1
  26. package/dist-lib/cloud.cjs +1 -1
  27. package/dist-lib/cloud.mjs +1 -1
  28. package/dist-lib/config.cjs +1 -1
  29. package/dist-lib/config.mjs +1 -1
  30. package/dist-lib/detox/index.cjs +6 -3
  31. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  32. package/dist-lib/expo-swift-bundle.cjs +280 -51
  33. package/dist-lib/expo-swift-metadata.cjs +1 -1
  34. package/dist-lib/expo-swift-metadata.mjs +1 -1
  35. package/dist-lib/home-paths.cjs +1 -1
  36. package/dist-lib/host/bridge-host.cjs +1264 -856
  37. package/dist-lib/host/fetch-proxy-handler.cjs +21 -5
  38. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  39. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  40. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  41. package/dist-lib/host/websocket-proxy.cjs +1 -1
  42. package/dist-lib/index.cjs +1966 -752
  43. package/dist-lib/jump-to-source-babel.cjs +1 -1
  44. package/dist-lib/jump-to-source-native.cjs +1 -1
  45. package/dist-lib/kotlin-host.cjs +1 -1
  46. package/dist-lib/menu.cjs +1 -1
  47. package/dist-lib/menu.mjs +1 -1
  48. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  49. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  50. package/dist-lib/metro-production-bundle.cjs +1 -1
  51. package/dist-lib/metro-production-bundle.mjs +1 -1
  52. package/dist-lib/metro.cjs +1806 -593
  53. package/dist-lib/native-app-identity-project.cjs +19 -3
  54. package/dist-lib/native-app-identity-project.mjs +19 -3
  55. package/dist-lib/native-app-identity.cjs +1 -1
  56. package/dist-lib/native-app-identity.mjs +1 -1
  57. package/dist-lib/native-firebase-metadata.cjs +1 -1
  58. package/dist-lib/native-firebase-metadata.mjs +1 -1
  59. package/dist-lib/pdfium-raster.cjs +1 -1
  60. package/dist-lib/pdfium-raster.mjs +1 -1
  61. package/dist-lib/plist.cjs +1 -1
  62. package/dist-lib/plist.mjs +1 -1
  63. package/dist-lib/profiles.cjs +1 -1
  64. package/dist-lib/public-brand.cjs +1 -1
  65. package/dist-lib/public-origins-lib-esm.json +25 -24
  66. package/dist-lib/public-origins-lib.json +83 -53
  67. package/dist-lib/react-native-host-modules.cjs +1 -1
  68. package/dist-lib/react-native-host-modules.mjs +1 -1
  69. package/dist-lib/reap-browser.cjs +1 -1
  70. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  71. package/dist-lib/sdk.cjs +2 -2
  72. package/dist-lib/sdk.mjs +2 -2
  73. package/dist-lib/skills.cjs +1800 -1334
  74. package/dist-lib/swift-compile.cjs +58 -12
  75. package/dist-lib/swift-compile.mjs +57 -12
  76. package/dist-lib/swift-package.cjs +1 -1
  77. package/dist-lib/swift-package.mjs +1 -1
  78. package/dist-lib/swift-submit.cjs +1 -1
  79. package/dist-lib/swift-submit.mjs +1 -1
  80. package/dist-lib/swift-toolchain.cjs +1 -1
  81. package/dist-lib/swift.cjs +1 -1
  82. package/dist-lib/vite-swift.cjs +257 -47
  83. package/dist-lib/vite.cjs +1530 -303
  84. package/dist-lib/worklet-executable.cjs +1 -1
  85. package/dist-lib/worklet-executable.mjs +1 -1
  86. package/dist-types/packages/peach-engine/src/engine/font-config.d.ts +1 -1
  87. package/dist-types/packages/peach-engine/src/engine/node.d.ts +1 -0
  88. package/kotlin/sdk.tar.gz +0 -0
  89. package/package.json +1 -1
  90. package/runtime-companion.json +1 -1
  91. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/CoreData.apinotes +35 -0
  92. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/CoreData.h +8 -0
  93. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/NSAttributeDescription.h +30 -0
  94. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/NSEntityDescription.h +14 -0
  95. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/NSExpressionDescription.h +10 -0
  96. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/NSFetchIndexDescription.h +13 -0
  97. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/NSFetchIndexElementDescription.h +18 -0
  98. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/NSManagedObject.h +11 -0
  99. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/NSManagedObjectContext.h +19 -0
  100. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/NSManagedObjectID.h +2 -0
  101. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/NSManagedObjectModel.h +3 -0
  102. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/NSMergePolicy.h +17 -0
  103. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/NSPersistentStore.h +8 -0
  104. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/NSPersistentStoreCoordinator.h +6 -0
  105. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/NSPropertyDescription.h +13 -0
  106. package/sdk/ios/System/Library/Frameworks/CoreData.framework/Headers/NSRelationshipDescription.h +21 -0
  107. package/sdk/ios/System/Library/Frameworks/QuartzCore.framework/Headers/CoreAnimation.h +2 -0
  108. package/sdk/ios/System/Library/Frameworks/UIKit.framework/Headers/UIImage.h +1 -10
  109. package/sdk/ios/System/Library/Frameworks/UIKit.framework/Headers/UIKit.apinotes +21 -0
  110. package/sdk/ios/System/Library/Frameworks/UIKit.framework/Headers/UINibLoading.h +1 -1
  111. package/sdk/ios/System/Library/Frameworks/UIKitTypes.framework/Headers/UIKitTypes.h +11 -0
  112. package/sdk/ios/usr/lib/swift/Combine.swiftmodule/arm64-apple-ios-simulator.swiftinterface +18 -0
  113. package/sdk/ios/usr/lib/swift/Foundation.swiftmodule/arm64-apple-ios-simulator.swiftinterface +1 -0
  114. package/sdk/ios/usr/lib/swift/SwiftUI.swiftmodule/arm64-apple-ios-simulator.swiftinterface +19 -0
  115. package/sdk/ios/usr/lib/swift/SwiftUICore.swiftmodule/arm64-apple-ios-simulator.swiftinterface +4 -0
  116. package/sdk/ios/usr/lib/swift/UIKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +4 -0
  117. package/sdk/ios/wasm/include/pthread.h +1 -0
  118. package/sdk/ios/wasm/include/signal.h +2 -2
  119. package/sdk/native-hashes.json +3 -3
  120. package/skills/contrast/SKILL.md +1 -0
  121. package/src/home-paths.ts +1 -1
  122. package/src/host/bridge-host.ts +101 -97
  123. package/src/metro-plugin.ts +4 -1
  124. package/src/native-static-libraries.ts +7 -0
  125. package/src/react-native-coverage/native-source-ranges-file.ts +1 -1
  126. package/src/runtime-assets.ts +0 -1
  127. package/src/runtime-delivery.ts +1 -1
  128. package/src/runtime-packs.ts +212 -0
  129. package/src/swift-compile.ts +48 -10
  130. package/src/swift-host.ts +22 -3
  131. package/src/swift-objc-compile.ts +1 -1
  132. package/src/swift-objc-import.ts +32 -2
  133. package/src/swift-objc-standin.ts +1 -1
  134. package/src/swift-packages.ts +53 -28
  135. package/src/swift-pdf-raster.ts +60 -4
  136. package/src/swift-shipped-archives.json +44 -0
  137. package/src/swift-shipped-binaries.ts +12 -0
  138. package/src/swift-standin-config.ts +4 -2
  139. package/src/swift-standin-emit.ts +9 -3
  140. package/src/swift-standins.ts +75 -8
  141. package/src/vite-plugin-one.ts +4 -1
@@ -55,6 +55,7 @@ import {
55
55
  sendFileWithRanges,
56
56
  } from '../runtime-assets.ts'
57
57
  import { rnxRuntime } from '../runtime-delivery.ts'
58
+ import { ensureRuntimePackFor } from '../runtime-packs.ts'
58
59
  import { handleTurboRequest, TURBO_ROUTE } from '../turbo-bundle.ts'
59
60
  import { AgentHost, type AgentHostOptions } from './agent-host.ts'
60
61
  import { serveAppPackageResource } from './app-package-handler'
@@ -1920,116 +1921,119 @@ export class SootSimBridgeHost {
1920
1921
  // inside baseDir that point outside — without it, an attacker who can
1921
1922
  // drop a symlink in the runtime dir gets arbitrary-read through the
1922
1923
  // daemon. realpath also collapses any remaining normalizable segments.
1923
- fs.realpath(resolved, (realErr, realResolved) => {
1924
- const servePath = realErr ? resolved : realResolved
1925
- const servePathWithSep = servePath.endsWith(path.sep)
1926
- ? servePath
1927
- : servePath + path.sep
1928
- if (!realErr) {
1929
- const realBaseWithSep = (() => {
1930
- try {
1931
- const rb = fs.realpathSync(baseDir)
1932
- return rb.endsWith(path.sep) ? rb : rb + path.sep
1933
- } catch {
1934
- return baseWithSep
1924
+ // a pack-owned prefix downloads on first request before the file resolves
1925
+ void ensureRuntimePackFor(baseDir, rel).then(() =>
1926
+ fs.realpath(resolved, (realErr, realResolved) => {
1927
+ const servePath = realErr ? resolved : realResolved
1928
+ const servePathWithSep = servePath.endsWith(path.sep)
1929
+ ? servePath
1930
+ : servePath + path.sep
1931
+ if (!realErr) {
1932
+ const realBaseWithSep = (() => {
1933
+ try {
1934
+ const rb = fs.realpathSync(baseDir)
1935
+ return rb.endsWith(path.sep) ? rb : rb + path.sep
1936
+ } catch {
1937
+ return baseWithSep
1938
+ }
1939
+ })()
1940
+ if (
1941
+ !servePathWithSep.startsWith(realBaseWithSep) &&
1942
+ servePath + path.sep !== realBaseWithSep
1943
+ ) {
1944
+ res.writeHead(403)
1945
+ res.end('forbidden')
1946
+ return
1935
1947
  }
1936
- })()
1937
- if (
1938
- !servePathWithSep.startsWith(realBaseWithSep) &&
1939
- servePath + path.sep !== realBaseWithSep
1940
- ) {
1941
- res.writeHead(403)
1942
- res.end('forbidden')
1943
- return
1944
1948
  }
1945
- }
1946
1949
 
1947
- fs.stat(servePath, (err, stats) => {
1948
- if (err || !stats?.isFile()) {
1949
- // SPA fallback — but only for extensionless paths. a 404 on
1950
- // /main.a1b2c3.js should 404, not return index.html as js,
1951
- // which the sim then chokes on.
1952
- const ext = path.extname(rel).toLowerCase()
1953
- if (ext && ext !== '.html') {
1954
- res.writeHead(404)
1955
- res.end('not found')
1956
- return
1957
- }
1958
- // never SPA-fallback internal endpoint namespaces — those are
1959
- // expected to return JSON (or a real 404) and the engine calls
1960
- // .json() on the response. without this, a tenant-worker fetch
1961
- // to an unimplemented /__foo or /api/foo gets a 200 + index.html
1962
- // and explodes with "Unexpected token '<', '<!doctype'... is not
1963
- // valid JSON". /__server-scan is the canonical case (see the
1964
- // handler above).
1965
- if (rel.startsWith('/__') || rel.startsWith('/api/') || rel === '/api') {
1966
- res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' })
1967
- res.end('not found')
1968
- return
1969
- }
1970
- const indexPath = path.join(baseDir, 'index.html')
1971
- fs.readFile(indexPath, (err2, data) => {
1972
- if (err2) {
1950
+ fs.stat(servePath, (err, stats) => {
1951
+ if (err || !stats?.isFile()) {
1952
+ // SPA fallback — but only for extensionless paths. a 404 on
1953
+ // /main.a1b2c3.js should 404, not return index.html as js,
1954
+ // which the sim then chokes on.
1955
+ const ext = path.extname(rel).toLowerCase()
1956
+ if (ext && ext !== '.html') {
1973
1957
  res.writeHead(404)
1974
1958
  res.end('not found')
1975
1959
  return
1976
1960
  }
1977
- res.writeHead(200, {
1978
- 'Content-Type': 'text/html; charset=utf-8',
1979
- 'Cache-Control': 'no-store',
1980
- })
1981
- if (method === 'HEAD') {
1982
- res.end()
1961
+ // never SPA-fallback internal endpoint namespaces — those are
1962
+ // expected to return JSON (or a real 404) and the engine calls
1963
+ // .json() on the response. without this, a tenant-worker fetch
1964
+ // to an unimplemented /__foo or /api/foo gets a 200 + index.html
1965
+ // and explodes with "Unexpected token '<', '<!doctype'... is not
1966
+ // valid JSON". /__server-scan is the canonical case (see the
1967
+ // handler above).
1968
+ if (rel.startsWith('/__') || rel.startsWith('/api/') || rel === '/api') {
1969
+ res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' })
1970
+ res.end('not found')
1983
1971
  return
1984
1972
  }
1985
- res.end(
1986
- injectSharedConfigIntoHtml(data, this.effectivePort, this.contrastOrigin),
1987
- )
1988
- })
1989
- return
1990
- }
1991
- const ext = path.extname(servePath).toLowerCase()
1992
- const contentType = HTTP_MIME_TYPES[ext] || 'application/octet-stream'
1993
- // no-store across the board: the runtime is served from a versioned
1994
- // directory but the HTTP path doesn't include the version, so the
1995
- // same URL serves different content after a runtime swap. caching
1996
- // any asset means a stale-bundle reload after runtime:changed.
1997
- res.writeHead(200, {
1998
- 'Content-Type': contentType,
1999
- 'Cache-Control': 'no-store',
2000
- // native file documents have an opaque origin when reading public fonts.
2001
- ...(publicEngineFont ? { 'Access-Control-Allow-Origin': '*' } : {}),
2002
- })
2003
- if (method === 'HEAD') {
2004
- res.end()
2005
- return
2006
- }
2007
- // html responses inline the daemon-injected `__sootsimSharedConfig`
2008
- // global so the engine's settingsStore initializes from disk on
2009
- // boot. for everything else stream the file straight through.
2010
- if (ext === '.html') {
2011
- fs.readFile(servePath, (readErr, data) => {
2012
- if (readErr) {
2013
- try {
1973
+ const indexPath = path.join(baseDir, 'index.html')
1974
+ fs.readFile(indexPath, (err2, data) => {
1975
+ if (err2) {
1976
+ res.writeHead(404)
1977
+ res.end('not found')
1978
+ return
1979
+ }
1980
+ res.writeHead(200, {
1981
+ 'Content-Type': 'text/html; charset=utf-8',
1982
+ 'Cache-Control': 'no-store',
1983
+ })
1984
+ if (method === 'HEAD') {
2014
1985
  res.end()
2015
- } catch {}
2016
- return
2017
- }
2018
- res.end(
2019
- injectSharedConfigIntoHtml(data, this.effectivePort, this.contrastOrigin),
2020
- )
1986
+ return
1987
+ }
1988
+ res.end(
1989
+ injectSharedConfigIntoHtml(data, this.effectivePort, this.contrastOrigin),
1990
+ )
1991
+ })
1992
+ return
1993
+ }
1994
+ const ext = path.extname(servePath).toLowerCase()
1995
+ const contentType = HTTP_MIME_TYPES[ext] || 'application/octet-stream'
1996
+ // no-store across the board: the runtime is served from a versioned
1997
+ // directory but the HTTP path doesn't include the version, so the
1998
+ // same URL serves different content after a runtime swap. caching
1999
+ // any asset means a stale-bundle reload after runtime:changed.
2000
+ res.writeHead(200, {
2001
+ 'Content-Type': contentType,
2002
+ 'Cache-Control': 'no-store',
2003
+ // native file documents have an opaque origin when reading public fonts.
2004
+ ...(publicEngineFont ? { 'Access-Control-Allow-Origin': '*' } : {}),
2021
2005
  })
2022
- return
2023
- }
2024
- const stream = fs.createReadStream(servePath)
2025
- stream.pipe(res)
2026
- stream.on('error', () => {
2027
- try {
2006
+ if (method === 'HEAD') {
2028
2007
  res.end()
2029
- } catch {}
2008
+ return
2009
+ }
2010
+ // html responses inline the daemon-injected `__sootsimSharedConfig`
2011
+ // global so the engine's settingsStore initializes from disk on
2012
+ // boot. for everything else stream the file straight through.
2013
+ if (ext === '.html') {
2014
+ fs.readFile(servePath, (readErr, data) => {
2015
+ if (readErr) {
2016
+ try {
2017
+ res.end()
2018
+ } catch {}
2019
+ return
2020
+ }
2021
+ res.end(
2022
+ injectSharedConfigIntoHtml(data, this.effectivePort, this.contrastOrigin),
2023
+ )
2024
+ })
2025
+ return
2026
+ }
2027
+ const stream = fs.createReadStream(servePath)
2028
+ stream.pipe(res)
2029
+ stream.on('error', () => {
2030
+ try {
2031
+ res.end()
2032
+ } catch {}
2033
+ })
2030
2034
  })
2031
- })
2032
- })
2035
+ }),
2036
+ )
2033
2037
  }
2034
2038
 
2035
2039
  private sweepIdleCliClients() {
@@ -51,6 +51,7 @@ import {
51
51
  serveRuntimeFile,
52
52
  SOOTSIM_RUNTIME_MISSING_MESSAGE,
53
53
  } from './runtime-assets'
54
+ import { ensureRuntimePackFor } from './runtime-packs'
54
55
  import {
55
56
  getRuntimePlatformAdapter,
56
57
  isRuntimePlatform,
@@ -451,7 +452,7 @@ export function withPeach<T extends Record<string, unknown>>(
451
452
 
452
453
  const server = connect()
453
454
 
454
- server.use((req: any, res: any, next: any) => {
455
+ server.use(async (req: any, res: any, next: any) => {
455
456
  if (isReplacementModuleRequestUrl(req.url)) {
456
457
  void handleReplacementModuleRequest(req, res, {
457
458
  servedRoots: [cfgProjectRoot, ...cfgWatchFolders],
@@ -501,6 +502,8 @@ export function withPeach<T extends Record<string, unknown>>(
501
502
  return
502
503
  }
503
504
 
505
+ await ensureRuntimePackFor(runtimeRoot, pathname)
506
+
504
507
  // root-relative runtime assets emitted by the shell and engine vite
505
508
  // builds. if a user app has its own /assets/foo request, we only
506
509
  // intercept when that exact file exists in the runtime; otherwise
@@ -192,6 +192,13 @@ export const NATIVE_STATIC_LIBRARY_RECIPES = [
192
192
  recipe: 'a2642115a5eeb04559c9f1daaa4b4cbe4b1c211533b4cc80cb8339a4477af205',
193
193
  archive: 'libsksg.a',
194
194
  },
195
+ {
196
+ name: 'webgpu_dawn',
197
+ nativeSha256: '95078993275bccc969b1dfa1ad0934cbacc05ae59e8aa2a6cc46ce93e97d4f69',
198
+ upstream: '3d786993a7ded64c4ebb4884b9b079db9ad0e580',
199
+ recipe: 'c55468cc976e837ad894133f2231beb7d1ed64633af7b457b8a2610d7df45994',
200
+ archive: 'libwebgpu_dawn.a',
201
+ },
195
202
  ]
196
203
 
197
204
  export function nativeStaticLibraryEntry(
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod'
2
- import rangesData from '../../../peach-compat/src/generated/native-source-ranges.json'
2
+ import rangesData from '../../../peach-compat/src/generated/native-source-ranges.json' with { type: 'json' }
3
3
  import type { NativeSourceRangesFile } from '../../../peach-compat/src/native-source-ranges-types'
4
4
 
5
5
  const run = z.object({
@@ -118,7 +118,6 @@ const ROOT_RUNTIME_PATHS = [
118
118
  '/fonts/',
119
119
  '/icons/',
120
120
  '/sounds/',
121
- '/spike/',
122
121
  '/test-wallpaper.jpg',
123
122
  '/preview-sw.js',
124
123
  '/preview/guest-bridge-host.js',
@@ -33,7 +33,7 @@ export const RNX_RUNTIME_PRODUCT: RuntimeProduct = {
33
33
  id: 'peach',
34
34
  defaultCdnOrigin: DEFAULT_RUNTIME_CDN_ORIGIN,
35
35
  manifestPath: '/runtimes/manifest.json',
36
- tarballPath: (version) => `/runtimes/sootsim-runtime-${version}.tar.gz`,
36
+ tarballPath: (version) => `/runtimes/sootsim-runtime-${version}.tar.zst`,
37
37
  // the engine runtime is a static SPA: shell at the root, engine/ and
38
38
  // engine-tenant/ beneath it. index.html is what the daemon and the
39
39
  // vite/metro plugins serve, so its absence means the tarball is not one.
@@ -0,0 +1,212 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { setTimeout } from 'node:timers'
4
+ import {
5
+ installVerifiedTarball,
6
+ RUNTIME_MERGE_PACK_DIR,
7
+ RUNTIME_MERGE_PACK_INSTALL,
8
+ RUNTIME_MERGE_PACK_MERGED,
9
+ RUNTIME_PACK_MARKER,
10
+ RUNTIME_PACK_PREFIXES,
11
+ RUNTIME_PACKS_FILE,
12
+ type RuntimePacks,
13
+ } from '@contrast/runtime-delivery'
14
+ import { rnxRuntime } from './runtime-delivery.ts'
15
+
16
+ // pieces only some apps reach ship beside the runtime archive, not in it. the
17
+ // archive carries `packs.json` naming each; the first request under a pack's
18
+ // prefix downloads it, checks its digest and lands it by rename, exactly as the
19
+ // archive itself installs. the desktop bundle keeps every prefix in place.
20
+ const inflight = new Map<string, Promise<void>>()
21
+ // an installed version's packs.json never changes
22
+ const packsByRoot = new Map<string, RuntimePacks | null>()
23
+ // listed pack files by basename, each with its runtime-relative path and pack
24
+ const listedByRoot = new Map<string, Map<string, { file: string; pack: string }[]>>()
25
+
26
+ function listedFiles(runtimeRoot: string) {
27
+ let listed = listedByRoot.get(runtimeRoot)
28
+ if (listed) return listed
29
+ listed = new Map()
30
+ for (const [pack, entry] of Object.entries(readPacks(runtimeRoot) ?? {}))
31
+ for (const file of entry.files ?? []) {
32
+ const name = path.posix.basename(file)
33
+ listed.set(name, [...(listed.get(name) ?? []), { file, pack }])
34
+ }
35
+ listedByRoot.set(runtimeRoot, listed)
36
+ return listed
37
+ }
38
+
39
+ function readPacks(runtimeRoot: string): RuntimePacks | null {
40
+ const cached = packsByRoot.get(runtimeRoot)
41
+ if (cached !== undefined) return cached
42
+ const file = path.join(runtimeRoot, RUNTIME_PACKS_FILE)
43
+ if (!fs.existsSync(file)) {
44
+ packsByRoot.set(runtimeRoot, null)
45
+ return null
46
+ }
47
+ const parsed: unknown = JSON.parse(fs.readFileSync(file, 'utf8'))
48
+ const packs: RuntimePacks = {}
49
+ if (typeof parsed !== 'object' || parsed === null)
50
+ throw new Error(`${file} is not an object`)
51
+ for (const [prefix, entry] of Object.entries(parsed)) {
52
+ if (typeof entry?.file !== 'string' || typeof entry?.sha256 !== 'string')
53
+ throw new Error(`${file} entry ${prefix} lacks file and sha256`)
54
+ const files: unknown = entry.files
55
+ if (
56
+ files !== undefined &&
57
+ !(Array.isArray(files) && files.every((f) => typeof f === 'string'))
58
+ )
59
+ throw new Error(`${file} entry ${prefix} has files that are not paths`)
60
+ packs[prefix] = {
61
+ file: entry.file,
62
+ sha256: entry.sha256,
63
+ ...(files ? { files } : {}),
64
+ }
65
+ }
66
+ packsByRoot.set(runtimeRoot, packs)
67
+ return packs
68
+ }
69
+
70
+ /** make sure the pack that owns a request path is on disk. never rejects: a
71
+ * pack that cannot download logs once and the file then answers 404. */
72
+ export async function ensureRuntimePackFor(
73
+ runtimeRoot: string,
74
+ pathname: string,
75
+ ): Promise<void> {
76
+ warmRuntimePacks(runtimeRoot)
77
+ const prefix = RUNTIME_PACK_PREFIXES.find((candidate) =>
78
+ pathname.includes(`/${candidate}`),
79
+ )
80
+ if (prefix) {
81
+ await installNamedPack(runtimeRoot, prefix)
82
+ return
83
+ }
84
+ const listed = listedFiles(runtimeRoot)
85
+ .get(path.posix.basename(pathname))
86
+ ?.find(({ file }) => pathname.endsWith(`/${file}`))
87
+ if (listed) {
88
+ if (!fs.existsSync(path.join(runtimeRoot, listed.file)))
89
+ await installNamedPack(runtimeRoot, listed.pack)
90
+ return
91
+ }
92
+ const at = pathname.indexOf(`/${RUNTIME_MERGE_PACK_DIR}`)
93
+ if (at === -1) return
94
+ const tenantDir = path.join(runtimeRoot, RUNTIME_MERGE_PACK_DIR)
95
+ const rel = pathname.slice(at + RUNTIME_MERGE_PACK_DIR.length + 1)
96
+ // the production pack holds only chunks; a missing source map or json is a plain 404
97
+ if (!/\.m?js$/.test(rel) || fs.existsSync(path.join(tenantDir, rel))) return
98
+ await installNamedPack(runtimeRoot, RUNTIME_MERGE_PACK_DIR)
99
+ }
100
+
101
+ function installNamedPack(runtimeRoot: string, name: string): Promise<boolean> {
102
+ if (name === RUNTIME_MERGE_PACK_DIR) {
103
+ const tenantDir = path.join(runtimeRoot, RUNTIME_MERGE_PACK_DIR)
104
+ return mergePack(
105
+ runtimeRoot,
106
+ name,
107
+ path.join(tenantDir, RUNTIME_MERGE_PACK_INSTALL),
108
+ tenantDir,
109
+ )
110
+ }
111
+ if (readPacks(runtimeRoot)?.[name]?.files)
112
+ return mergePack(
113
+ runtimeRoot,
114
+ name,
115
+ path.join(runtimeRoot, `.pack-${name}`),
116
+ runtimeRoot,
117
+ )
118
+ return installPack(runtimeRoot, name, path.join(runtimeRoot, name.slice(0, -1)))
119
+ }
120
+
121
+ // once the first page has loaded, fetch every remaining pack one at a time so
122
+ // later use never waits on the network. a request for a pack joins its download;
123
+ // a failed download ends the warm, so an offline machine logs once.
124
+ const WARM_DELAY_MS = 30_000
125
+ const warming = new Set<string>()
126
+
127
+ function warmRuntimePacks(runtimeRoot: string): void {
128
+ if (warming.has(runtimeRoot)) return
129
+ warming.add(runtimeRoot)
130
+ const warm = async () => {
131
+ for (const name of Object.keys(readPacks(runtimeRoot) ?? {}))
132
+ if (!(await installNamedPack(runtimeRoot, name))) return
133
+ }
134
+ const schedule: (fn: () => void, ms: number) => NodeJS.Timeout = setTimeout
135
+ schedule(() => void warm(), WARM_DELAY_MS).unref()
136
+ }
137
+
138
+ // a merge pack installs beside the tree it belongs to, then hard-links its files
139
+ // into place so the tree keeps one copy on disk. a link that already exists came
140
+ // from an earlier or concurrent merge of the same bytes.
141
+ async function mergePack(
142
+ runtimeRoot: string,
143
+ name: string,
144
+ installDir: string,
145
+ targetDir: string,
146
+ ): Promise<boolean> {
147
+ if (fs.existsSync(path.join(installDir, RUNTIME_MERGE_PACK_MERGED))) return true
148
+ if (!(await installPack(runtimeRoot, name, installDir))) return false
149
+ const link = (sub: string): void => {
150
+ for (const entry of fs.readdirSync(path.join(installDir, sub), {
151
+ withFileTypes: true,
152
+ })) {
153
+ const child = path.join(sub, entry.name)
154
+ if (entry.isDirectory()) {
155
+ fs.mkdirSync(path.join(targetDir, child), { recursive: true })
156
+ link(child)
157
+ } else if (entry.name !== RUNTIME_PACK_MARKER) {
158
+ try {
159
+ fs.linkSync(path.join(installDir, child), path.join(targetDir, child))
160
+ } catch (error) {
161
+ if (!(error instanceof Error && 'code' in error && error.code === 'EEXIST'))
162
+ throw error
163
+ }
164
+ }
165
+ }
166
+ }
167
+ try {
168
+ link('')
169
+ fs.writeFileSync(path.join(installDir, RUNTIME_MERGE_PACK_MERGED), '')
170
+ return true
171
+ } catch (error) {
172
+ console.error(
173
+ `[peach] ${name} pack merge failed: ${error instanceof Error ? error.message : String(error)}`,
174
+ )
175
+ return false
176
+ }
177
+ }
178
+
179
+ async function installPack(
180
+ runtimeRoot: string,
181
+ prefix: string,
182
+ destDir: string,
183
+ ): Promise<boolean> {
184
+ const marker = path.join(destDir, RUNTIME_PACK_MARKER)
185
+ if (fs.existsSync(marker)) return true
186
+ const pack = readPacks(runtimeRoot)?.[prefix]
187
+ // a runtime that shipped the prefix inline has no pack entry
188
+ if (!pack) return true
189
+ let install = inflight.get(destDir)
190
+ if (!install) {
191
+ const version = path.basename(runtimeRoot)
192
+ // the hash in the url keeps a refreshed pre-release from hitting a stale cached pack
193
+ const url = new URL(pack.file, rnxRuntime.tarballUrl(version))
194
+ url.searchParams.set('sha256', pack.sha256)
195
+ install = installVerifiedTarball({
196
+ url: url.toString(),
197
+ digest: { algorithm: 'sha256', encoding: 'hex', value: pack.sha256 },
198
+ destDir,
199
+ installedMarker: RUNTIME_PACK_MARKER,
200
+ label: `peach runtime ${version} pack ${prefix}`,
201
+ })
202
+ .catch((error: unknown) => {
203
+ console.error(
204
+ `[peach] ${prefix} pack download failed: ${error instanceof Error ? error.message : String(error)}`,
205
+ )
206
+ })
207
+ .finally(() => inflight.delete(destDir))
208
+ inflight.set(destDir, install)
209
+ }
210
+ await install
211
+ return fs.existsSync(marker)
212
+ }
@@ -62,6 +62,7 @@ import {
62
62
  SWIFT_OBJC_INTEROP_FLAGS,
63
63
  swiftClangImporterFlags,
64
64
  swiftObjCModuleMap,
65
+ swiftSubmoduleImports,
65
66
  } from './swift-objc-import'
66
67
  import { swiftObjCRuntimeFlags } from './swift-objc-runtime'
67
68
  import { iosDeploymentMinimum, objcSdkIntakeFlags } from './swift-objc-standin'
@@ -3832,6 +3833,25 @@ function stripCCode(source: string): string {
3832
3833
  return code
3833
3834
  }
3834
3835
 
3836
+ // whether the unit's code under wasm32 takes its own return address, which
3837
+ // the WebAssembly backend rejects ("hasn't implemented __builtin_return_address"):
3838
+ // native machine code the target has no lowering for, like assembly. branches
3839
+ // the native architecture macros guard are not wasm code.
3840
+ const wasmMacroEnvironment: SwiftCMacroEnvironment = {
3841
+ ...emptyMacroEnvironment,
3842
+ defines: new Map([
3843
+ ['__wasm__', '1'],
3844
+ ['__wasm32__', '1'],
3845
+ ['__wasi__', '1'],
3846
+ ]),
3847
+ }
3848
+ export function swiftCTakesReturnAddress(source: string): boolean {
3849
+ if (!source.includes('__builtin_return_address')) return false
3850
+ return /\b__builtin_return_address\s*\(/.test(
3851
+ cActiveCode(stripCCode(source), wasmMacroEnvironment),
3852
+ )
3853
+ }
3854
+
3835
3855
  // every name the given sources define. order and conditions are not
3836
3856
  // evaluated: a name defined anywhere makes its guards undecided.
3837
3857
  export function swiftCDefinedNames(sources: Iterable<string>): Set<string> {
@@ -4360,6 +4380,11 @@ export function writeSwiftProject(
4360
4380
  clangImportedModules.add(name)
4361
4381
  }
4362
4382
  }
4383
+ const submoduleImports = swiftSubmoduleImports(
4384
+ Object.entries(input.files).flatMap(([file, contents]) =>
4385
+ file.endsWith('.swift') ? [contents] : [],
4386
+ ),
4387
+ )
4363
4388
  const headerDirectory = path.join(layout.root, '.build', 'peach-cxx-headers')
4364
4389
  const flattenedHeaders = new Set<string>()
4365
4390
  // product header aliases belong to this input. keep emitted Swift headers,
@@ -4906,6 +4931,7 @@ export function writeSwiftProject(
4906
4931
  swiftObjCModuleMap(
4907
4932
  swiftTargetModuleName(target.name),
4908
4933
  roots.map((file) => path.join(layout.root, file)),
4934
+ submoduleImports.get(swiftTargetModuleName(target.name)),
4909
4935
  ))
4910
4936
  if (moduleContents === undefined)
4911
4937
  throw new Error('missing original module map ' + modulemap)
@@ -5295,13 +5321,17 @@ export function writeSwiftProject(
5295
5321
  modulemap,
5296
5322
  originalMap
5297
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`
5298
- : swiftObjCModuleMap(moduleName, [
5299
- ...(
5300
- input.nativeTargets?.find((target) => target.name === name)
5301
- ?.publicHeaders ?? []
5302
- ).map((header) => path.join(layout.root, header)),
5303
- path.join(headerDirectory, name + '-Swift.h'),
5304
- ]),
5324
+ : swiftObjCModuleMap(
5325
+ 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
+ ],
5333
+ submoduleImports.get(moduleName),
5334
+ ),
5305
5335
  )
5306
5336
  nativeHeaderFlags.push('-fmodule-map-file=' + modulemap)
5307
5337
  }
@@ -6588,8 +6618,15 @@ async function buildFrameworkProducts(
6588
6618
  const linked = frameworkLinkClosure(scratch, linkedTargets.targets)
6589
6619
  const objects = linked.objects
6590
6620
  if (linked.modules.has('UniformTypeIdentifiers')) {
6591
- const object = path.join(scratch, 'UniformTypeIdentifiers-NativeConstants.o')
6592
- if (!existsSync(object)) {
6621
+ const constantsDir = path.join(scratch, 'uniform-type-constants')
6622
+ const constantsStamp = path.join(constantsDir, '.complete')
6623
+ const object = path.join(constantsDir, 'UniformTypeIdentifiers-NativeConstants.o')
6624
+ if (
6625
+ !existsSync(constantsStamp) ||
6626
+ readFileSync(constantsStamp, 'utf8') !== args.frameworkKey
6627
+ ) {
6628
+ rmSync(constantsDir, { recursive: true, force: true })
6629
+ mkdirSync(constantsDir)
6593
6630
  const sdkPaths = swiftSdkPaths(args.moduleRoot, args.toolchainBin)
6594
6631
  compileUniformTypeConstants({
6595
6632
  bin: args.toolchainBin,
@@ -6597,8 +6634,9 @@ async function buildFrameworkProducts(
6597
6634
  darwin: sdkPaths.darwin,
6598
6635
  packageRoot: args.moduleRoot,
6599
6636
  minimum: darwinRecordsMinimum(),
6600
- out: scratch,
6637
+ out: constantsDir,
6601
6638
  })
6639
+ writeFileSync(constantsStamp, args.frameworkKey)
6602
6640
  }
6603
6641
  objects.push(object)
6604
6642
  linked.owners.set(object, 'UniformTypeIdentifiers')
package/src/swift-host.ts CHANGED
@@ -83,7 +83,8 @@ import {
83
83
  type SwiftPackageGap,
84
84
  type SwiftPackages,
85
85
  } from './swift-packages'
86
- import { rasterizePdfFile } from './swift-pdf-raster'
86
+ import { rasterizePdfFile, rasterizeSvgFile } from './swift-pdf-raster'
87
+ import { shippedBinaryDiffers } from './swift-shipped-binaries'
87
88
  import {
88
89
  packageInputPath,
89
90
  readPackageFile as readSwiftSourceFile,
@@ -596,7 +597,7 @@ async function swiftPackages(
596
597
  // compiling, or a placeholder package's swift target: each stands in from
597
598
  // its public surface.
598
599
  const standinArtifact = (binary: SwiftPackageBinary) =>
599
- binary.publicHeaders === undefined || binary.assemblySources !== undefined
600
+ binary.publicHeaders === undefined || binary.unloweredSources !== undefined
600
601
  const artifacts = reached.filter(standinArtifact)
601
602
  if (entry.moduleRoot && entry.provided && artifacts.length > 0) {
602
603
  const resolved = swiftModuleToolchain(entry.moduleRoot)
@@ -618,10 +619,16 @@ async function swiftPackages(
618
619
  const cxxArtifacts = reached.filter(standinArtifact)
619
620
  const generation = standinGenerationInputs(cxxArtifacts, toolchain, entry.behavior)
620
621
  if (cached.standinKey !== generation) {
622
+ // an archive other than the one peach ships a module for stands in itself
623
+ const replaced = new Set(
624
+ cxxArtifacts
625
+ .filter((binary) => shippedBinaryDiffers(binary.target, binary.checksum))
626
+ .map((binary) => binary.target),
627
+ )
621
628
  const built = await swiftStandins(
622
629
  cxxArtifacts,
623
630
  toolchain,
624
- entry.provided,
631
+ new Set([...entry.provided].filter((name) => !replaced.has(name))),
625
632
  entry.behavior,
626
633
  )
627
634
  cached.packages = await resolveSwiftPackagesAsync({
@@ -637,6 +644,9 @@ async function swiftPackages(
637
644
  .map((binary) => binary.target)
638
645
  .filter((target) => !built.gaps.has(target)),
639
646
  ]),
647
+ standinTargets: new Set(
648
+ [...replaced].filter((target) => !built.gaps.has(target)),
649
+ ),
640
650
  binaryGaps: built.gaps,
641
651
  owners,
642
652
  })
@@ -1941,6 +1951,15 @@ export function buildSwiftResourceTable(
1941
1951
  throw new Error('swift image pack exceeds the compile service caps')
1942
1952
  }
1943
1953
  bytes = readPackageFile(project.packagePath, disk, limits.imageBytes)
1954
+ const svg =
1955
+ ext === '.svg'
1956
+ ? rasterizeSvgFile(disk, createHash('sha256').update(bytes).digest('hex'))
1957
+ : null
1958
+ if (svg) {
1959
+ bytes = svg.bytes
1960
+ ext = '.png'
1961
+ uploadName = `${path.posix.basename(file, path.posix.extname(file))}.png`
1962
+ }
1944
1963
  }
1945
1964
  if (imageBytes + bytes.length > limits.imageTotalBytes) {
1946
1965
  throw new Error('swift image pack exceeds the compile service caps')