peachsim 0.1.638 → 0.1.640

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 (81) hide show
  1. package/cli/command-registry.ts +1 -0
  2. package/cli/commands/control.ts +75 -6
  3. package/cli/commands/swift.ts +326 -0
  4. package/cli/main.ts +11 -0
  5. package/cli/outbound-endpoints.ts +5 -1
  6. package/dist-lib/agent-daemon-client.cjs +1 -1
  7. package/dist-lib/agent-events.cjs +1 -1
  8. package/dist-lib/agent-identity.cjs +1 -1
  9. package/dist-lib/agent-sessions.cjs +1 -1
  10. package/dist-lib/attached-projects.cjs +1 -1
  11. package/dist-lib/auth/shared-session.cjs +1 -1
  12. package/dist-lib/backend-origin.cjs +1 -1
  13. package/dist-lib/beta.cjs +1 -1
  14. package/dist-lib/beta.mjs +1 -1
  15. package/dist-lib/bridge-constants.cjs +1 -1
  16. package/dist-lib/bridge-contract-input.cjs +1 -1
  17. package/dist-lib/bridge-contract-input.mjs +1 -1
  18. package/dist-lib/bridge-contract.cjs +1 -1
  19. package/dist-lib/bridge-contract.mjs +1 -1
  20. package/dist-lib/capture-contract.cjs +1 -1
  21. package/dist-lib/capture-contract.mjs +1 -1
  22. package/dist-lib/cli-constants.cjs +1 -1
  23. package/dist-lib/cloud-contract.cjs +1 -1
  24. package/dist-lib/cloud-contract.mjs +1 -1
  25. package/dist-lib/cloud-test-args.cjs +1 -1
  26. package/dist-lib/cloud-test-args.mjs +1 -1
  27. package/dist-lib/cloud.cjs +1 -1
  28. package/dist-lib/cloud.mjs +1 -1
  29. package/dist-lib/config.cjs +1 -1
  30. package/dist-lib/detox/index.cjs +1 -1
  31. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  32. package/dist-lib/home-paths.cjs +1 -1
  33. package/dist-lib/host/bridge-host.cjs +1 -1
  34. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  35. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  36. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  37. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  38. package/dist-lib/host/websocket-proxy.cjs +1 -1
  39. package/dist-lib/index.cjs +13 -4
  40. package/dist-lib/jump-to-source-babel.cjs +1 -1
  41. package/dist-lib/jump-to-source-native.cjs +1 -1
  42. package/dist-lib/kotlin-host.cjs +1 -1
  43. package/dist-lib/menu.cjs +1 -1
  44. package/dist-lib/menu.mjs +1 -1
  45. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  46. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  47. package/dist-lib/metro-production-bundle.cjs +1 -1
  48. package/dist-lib/metro-production-bundle.mjs +1 -1
  49. package/dist-lib/metro.cjs +1 -1
  50. package/dist-lib/native-app-identity.cjs +1 -1
  51. package/dist-lib/native-app-identity.mjs +1 -1
  52. package/dist-lib/profiles.cjs +1 -1
  53. package/dist-lib/public-brand.cjs +1 -1
  54. package/dist-lib/react-native-host-modules.cjs +1 -1
  55. package/dist-lib/react-native-host-modules.mjs +1 -1
  56. package/dist-lib/render-mode.cjs +1 -1
  57. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  58. package/dist-lib/sdk.cjs +1 -1
  59. package/dist-lib/sdk.mjs +1 -1
  60. package/dist-lib/skills.cjs +389 -232
  61. package/dist-lib/swift-compile.cjs +1 -1
  62. package/dist-lib/swift-compile.mjs +1 -1
  63. package/dist-lib/swift-package.cjs +1 -1
  64. package/dist-lib/swift-package.mjs +1 -1
  65. package/dist-lib/swift-submit.cjs +89 -8
  66. package/dist-lib/swift-submit.mjs +78 -8
  67. package/dist-lib/swift.cjs +16 -33308
  68. package/dist-lib/vite-swift.cjs +422 -247
  69. package/dist-lib/vite.cjs +13 -4
  70. package/package.json +1 -1
  71. package/skills/contrast/SKILL.md +1 -0
  72. package/src/assetCatalogContract.ts +16 -0
  73. package/src/dot-env.ts +148 -0
  74. package/src/metro-runtime-source.ts +15 -0
  75. package/src/swift-guest.ts +51 -0
  76. package/src/swift-host.ts +900 -0
  77. package/src/swift-submit.ts +97 -6
  78. package/src/swift.ts +36 -8
  79. package/src/swiftContract.ts +52 -0
  80. package/src/vite-plugin-one.ts +4 -3
  81. package/src/vite-plugin-swift.ts +43 -755
@@ -21,6 +21,18 @@
21
21
  */
22
22
 
23
23
  export const SWIFT_COMPILE_PATH = '/api/swift-compile'
24
+ export const SWIFT_PACKAGE_PATH = '/__peach/swift-package/'
25
+ export const SWIFT_HMR_PATH = '/__peach/swift-hmr'
26
+
27
+ // shared with the compile service; collection enforces these before reading.
28
+ export const MAX_SWIFT_COMPILE_FILES = 256
29
+ export const MAX_SWIFT_COMPILE_SOURCE_BYTES = 2 * 1024 * 1024
30
+ export const MAX_SWIFT_COMPILE_RESOURCES = 128
31
+ export const MAX_SWIFT_COMPILE_RESOURCE_BYTES = 2 * 1024 * 1024
32
+ export const MAX_SWIFT_COMPILE_RESOURCE_TOTAL_BYTES = 8 * 1024 * 1024
33
+ export const MAX_SWIFT_COMPILE_IMAGES = 128
34
+ export const MAX_SWIFT_COMPILE_IMAGE_BYTES = 4 * 1024 * 1024
35
+ export const MAX_SWIFT_COMPILE_IMAGE_TOTAL_BYTES = 16 * 1024 * 1024
24
36
 
25
37
  /**
26
38
  * what an artifact needs from the mount that instantiates it. every request
@@ -31,8 +43,9 @@ export const SWIFT_COMPILE_PATH = '/api/swift-compile'
31
43
  * its host. 1: the host fills Sources/PeachICU's reserved icu data and provides
32
44
  * `peach.icu_data_v1` (`packages/sootsim-swift/src/icuData.ts`). a request
33
45
  * without one comes from before this field, a host that is 0.
46
+ * 2: scroll anchors carry numeric unit coordinates to the kit.
34
47
  */
35
- export const SWIFT_HOST_ABI = 1
48
+ export const SWIFT_HOST_ABI = 2
36
49
 
37
50
  export interface SwiftImageUpload {
38
51
  /** the raster file picked for the asset (has the extension). */
@@ -142,6 +155,7 @@ export interface SwiftCompileContext {
142
155
  * app must pass one.
143
156
  */
144
157
  endpoint?: string
158
+ signal?: AbortSignal
145
159
  }
146
160
 
147
161
  export type SwiftCompileSubmit = (work: {
@@ -170,6 +184,7 @@ export function httpSwiftCompileSubmit(
170
184
  try {
171
185
  response = await fetch(url, {
172
186
  method: 'POST',
187
+ signal: context.signal,
173
188
  headers: {
174
189
  'content-type': 'application/json',
175
190
  ...(authorization ? { authorization } : {}),
@@ -192,8 +207,68 @@ export function httpSwiftCompileSubmit(
192
207
  `contrast-bundler: the swift compile for ${work.entry} answered ${response.status}${detail ? `: ${detail}` : ''}`,
193
208
  )
194
209
  }
195
- return (await response.json()) as SwiftCompileOutcome
210
+ const outcome: unknown = await response.json()
211
+ if (!isSwiftCompileOutcome(outcome)) {
212
+ throw new Error('swift compile: the service returned invalid artifact metadata')
213
+ }
214
+ return outcome
215
+ }
216
+ }
217
+
218
+ function isRecord(value: unknown): value is Record<string, unknown> {
219
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
220
+ }
221
+
222
+ export function isSwiftNativeModules(value: unknown): value is SwiftNativeModules {
223
+ return (
224
+ isRecord(value) &&
225
+ typeof value.packageId === 'string' &&
226
+ typeof value.hash === 'string' &&
227
+ isRecord(value.files) &&
228
+ isRecord(value.modules) &&
229
+ [...Object.values(value.files), ...Object.values(value.modules)].every(
230
+ (names) => Array.isArray(names) && names.every((name) => typeof name === 'string'),
231
+ ) &&
232
+ Array.isArray(value.defaultViewFiles) &&
233
+ value.defaultViewFiles.every((file) => typeof file === 'string')
234
+ )
235
+ }
236
+
237
+ function isSwiftCompileOutcome(value: unknown): value is SwiftCompileOutcome {
238
+ if (!isRecord(value)) return false
239
+ switch (value.status) {
240
+ case 'published':
241
+ return (
242
+ typeof value.artifactUrl === 'string' &&
243
+ typeof value.icuUrl === 'string' &&
244
+ typeof value.hash === 'string' &&
245
+ /^[a-f0-9]{64}$/.test(value.hash) &&
246
+ (value.imageUrls === undefined ||
247
+ (isRecord(value.imageUrls) &&
248
+ Object.values(value.imageUrls).every((url) => typeof url === 'string'))) &&
249
+ (value.native === undefined || isSwiftNativeModules(value.native))
250
+ )
251
+ case 'compiling':
252
+ return typeof value.hash === 'string' && /^[a-f0-9]{64}$/.test(value.hash)
253
+ case 'refused':
254
+ case 'failed':
255
+ return typeof value.detail === 'string'
256
+ default:
257
+ return false
258
+ }
259
+ }
260
+
261
+ function compileAssetUrl(value: string, origin: string): string {
262
+ const url = new URL(value, origin)
263
+ if (
264
+ (url.protocol !== 'http:' && url.protocol !== 'https:') ||
265
+ url.username ||
266
+ url.password ||
267
+ url.origin !== new URL(origin).origin
268
+ ) {
269
+ throw new Error('swift compile: artifact address escapes the compile origin')
196
270
  }
271
+ return url.href
197
272
  }
198
273
 
199
274
  export interface SwiftModuleAddress {
@@ -215,7 +290,7 @@ function resolveImageUrls(
215
290
  ): Record<string, string> {
216
291
  const resolved: Record<string, string> = {}
217
292
  for (const [name, url] of Object.entries(urls)) {
218
- resolved[name] = new URL(url, origin).href
293
+ resolved[name] = compileAssetUrl(url, origin)
219
294
  }
220
295
  return resolved
221
296
  }
@@ -239,22 +314,38 @@ export async function compileSwiftProject(
239
314
  packageId?: string
240
315
  },
241
316
  origin: string,
317
+ signal?: AbortSignal,
242
318
  ): Promise<SwiftModuleAddress> {
243
319
  while (true) {
320
+ signal?.throwIfAborted()
244
321
  const outcome = await submit(work)
245
322
  switch (outcome.status) {
246
323
  case 'published':
247
324
  return {
248
- url: new URL(outcome.artifactUrl, origin).href,
325
+ url: compileAssetUrl(outcome.artifactUrl, origin),
249
326
  hash: outcome.hash,
250
- icuUrl: new URL(outcome.icuUrl, origin).href,
327
+ icuUrl: compileAssetUrl(outcome.icuUrl, origin),
251
328
  ...(outcome.imageUrls === undefined
252
329
  ? {}
253
330
  : { imageUrls: resolveImageUrls(outcome.imageUrls, origin) }),
254
331
  ...(outcome.native === undefined ? {} : { native: outcome.native }),
255
332
  }
256
333
  case 'compiling':
257
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))
334
+ await new Promise<void>((resolve, reject) => {
335
+ const finish = () => {
336
+ signal?.removeEventListener('abort', abort)
337
+ resolve()
338
+ }
339
+ const timer = setTimeout(finish, POLL_INTERVAL_MS)
340
+ const abort = () => {
341
+ clearTimeout(timer)
342
+ signal?.removeEventListener('abort', abort)
343
+ const reason = signal?.reason
344
+ reject(reason instanceof Error ? reason : new Error(String(reason)))
345
+ }
346
+ signal?.addEventListener('abort', abort, { once: true })
347
+ if (signal?.aborted) abort()
348
+ })
258
349
  continue
259
350
  case 'refused':
260
351
  case 'failed':
package/src/swift.ts CHANGED
@@ -1,9 +1,37 @@
1
- // the runtime the `.swift` vite transform emits its module against. the
2
- // implementation lives beside the toolchain it drives, in sootsim-swift.
3
- export {
4
- createSwiftEntry,
5
- callSwiftModule,
6
- useSwiftHost,
1
+ // the public entry calls the existing guest host; the engine owns the mount.
2
+ import {
3
+ isSwiftRuntimeHost,
7
4
  type SwiftEntryOptions,
8
- type SwiftModuleOptions,
9
- } from '../../sootsim-swift/src/mount.tsx'
5
+ type SwiftRuntimeHost,
6
+ } from './swiftContract'
7
+ export type { SwiftEntryOptions, SwiftModuleOptions } from './swiftContract'
8
+
9
+ function swiftHost(): SwiftRuntimeHost {
10
+ const modules: unknown = Reflect.get(globalThis, '__contrastHostModules')
11
+ const host: unknown =
12
+ typeof modules === 'object' && modules !== null
13
+ ? Reflect.get(modules, 'peachsim/swift')
14
+ : undefined
15
+ if (!isSwiftRuntimeHost(host)) {
16
+ throw new Error('peachsim/swift requires the Peach runtime host')
17
+ }
18
+ return host
19
+ }
20
+
21
+ export function createSwiftEntry(options: SwiftEntryOptions) {
22
+ return swiftHost().createSwiftEntry(options)
23
+ }
24
+
25
+ export function useSwiftHost(options: SwiftEntryOptions, props: Record<string, unknown>) {
26
+ return swiftHost().useSwiftHost(options, props)
27
+ }
28
+
29
+ export function callSwiftModule(
30
+ options: SwiftEntryOptions,
31
+ module: string,
32
+ method: string,
33
+ args: unknown[],
34
+ contractHash: string,
35
+ ) {
36
+ return swiftHost().callSwiftModule(options, module, method, args, contractHash)
37
+ }
@@ -0,0 +1,52 @@
1
+ import type {
2
+ AppCatalogColor,
3
+ SfSymbolVector,
4
+ SwiftImageSource,
5
+ } from './assetCatalogContract'
6
+ import type { ComponentType } from 'react'
7
+
8
+ export interface SwiftModuleOptions {
9
+ // the host serves the artifact, icu data and named image bytes.
10
+ url: string
11
+ // a changed hash hot reloads the shared package instance with its state.
12
+ hash: string
13
+ icuUrl?: string
14
+ exports?: readonly string[]
15
+ resources?: Record<string, SwiftImageSource>
16
+ // main-bundle files are base64 bytes exposed to wasm through its read-only /res.
17
+ bundleFiles?: Record<string, string>
18
+ colors?: Record<string, AppCatalogColor>
19
+ symbols?: Record<string, SfSymbolVector>
20
+ }
21
+
22
+ export interface SwiftEntryOptions extends SwiftModuleOptions {
23
+ // only the standalone preview owns the app bundle; imports retain the host catalog.
24
+ mainBundle?: boolean
25
+ // the package/source path keeps a root's identity across module reloads.
26
+ rootId?: string
27
+ }
28
+
29
+ export interface SwiftRuntimeHost {
30
+ createSwiftEntry(options: SwiftEntryOptions): ComponentType<Record<string, unknown>>
31
+ useSwiftHost(options: SwiftEntryOptions, props: Record<string, unknown>): ComponentType
32
+ callSwiftModule(
33
+ options: SwiftEntryOptions,
34
+ module: string,
35
+ method: string,
36
+ args: unknown[],
37
+ contractHash: string,
38
+ ): Promise<unknown>
39
+ }
40
+
41
+ export function isSwiftRuntimeHost(host: unknown): host is SwiftRuntimeHost {
42
+ return !(
43
+ typeof host !== 'object' ||
44
+ host === null ||
45
+ !('createSwiftEntry' in host) ||
46
+ typeof host.createSwiftEntry !== 'function' ||
47
+ !('useSwiftHost' in host) ||
48
+ typeof host.useSwiftHost !== 'function' ||
49
+ !('callSwiftModule' in host) ||
50
+ typeof host.callSwiftModule !== 'function'
51
+ )
52
+ }
@@ -28,10 +28,10 @@ import {
28
28
  serveRuntimeFile,
29
29
  SOOTSIM_RUNTIME_MISSING_MESSAGE,
30
30
  } from './runtime-assets'
31
+ import { SWIFT_HMR_PATH, SWIFT_PACKAGE_PATH } from './swift-submit'
31
32
  import type { PeachPluginOptions } from './vite'
32
33
  import type { Plugin } from 'vite'
33
34
 
34
- const SWIFT_HMR_PATH = '/__peach/swift-hmr'
35
35
  // every swift route sits under this prefix. the swift host (compiler client,
36
36
  // resource packer, module glue) loads on the first request for one, so an app
37
37
  // that imports no swift never evaluates it.
@@ -154,7 +154,8 @@ export function rnxPlugin(options: PeachPluginOptions = {}): Plugin {
154
154
  })
155
155
  .catch(next)
156
156
  })
157
- server.watcher.on('change', (file) => {
157
+ server.watcher.on('all', (event, file) => {
158
+ if (event !== 'add' && event !== 'unlink' && event !== 'change') return
158
159
  if (activeSwiftPackages.size === 0) return
159
160
  const moduleRoot = moduleRootFor()
160
161
  let packages: Map<string, string>
@@ -209,7 +210,7 @@ export function rnxPlugin(options: PeachPluginOptions = {}): Plugin {
209
210
  loadSwiftHost()
210
211
  .then((host) => {
211
212
  if (host.serveSwiftPackageAsset(pathname, res)) return
212
- const prefix = '/__peach/swift-package/'
213
+ const prefix = SWIFT_PACKAGE_PATH
213
214
  if (!pathname.startsWith(prefix)) return next()
214
215
  if (!swiftSockets) {
215
216
  res.statusCode = 501