peachsim 0.1.570 → 0.1.572

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 (242) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +108 -108
  3. package/cli/app-config.ts +1 -1
  4. package/cli/app-fonts.ts +4 -4
  5. package/cli/auth.ts +11 -11
  6. package/cli/bin.ts +4 -4
  7. package/cli/bridge-flow-runner.ts +28 -20
  8. package/cli/browser-evals.ts +2 -2
  9. package/cli/cli-update.ts +6 -6
  10. package/cli/cloud-client.ts +16 -16
  11. package/cli/cloud-dispatch.ts +3 -2
  12. package/cli/cloud-session.ts +12 -12
  13. package/cli/cloud-test-result-files.ts +97 -0
  14. package/cli/cloud-test-tunnel.ts +4 -4
  15. package/cli/command-registry.ts +7 -7
  16. package/cli/commands/agent-wrapper.ts +7 -7
  17. package/cli/commands/agent.ts +16 -16
  18. package/cli/commands/app-fonts.ts +3 -3
  19. package/cli/commands/assert.ts +10 -10
  20. package/cli/commands/auth.ts +5 -5
  21. package/cli/commands/box/checkout-plane.ts +1 -1
  22. package/cli/commands/box/plane-share.ts +2 -2
  23. package/cli/commands/box/rnx-command.ts +5 -5
  24. package/cli/commands/box.ts +38 -38
  25. package/cli/commands/camera.ts +13 -13
  26. package/cli/commands/cleanup.ts +13 -13
  27. package/cli/commands/compat.ts +7 -7
  28. package/cli/commands/config.ts +5 -5
  29. package/cli/commands/control.ts +63 -63
  30. package/cli/commands/cpu-profile.ts +11 -11
  31. package/cli/commands/daemon-mac-app.ts +10 -10
  32. package/cli/commands/daemon.ts +29 -27
  33. package/cli/commands/debug.ts +35 -35
  34. package/cli/commands/desktop.ts +3 -3
  35. package/cli/commands/detox.ts +33 -33
  36. package/cli/commands/device.ts +9 -9
  37. package/cli/commands/diagnose.ts +17 -17
  38. package/cli/commands/do-chain.ts +4 -4
  39. package/cli/commands/electron.ts +9 -9
  40. package/cli/commands/film.ts +9 -9
  41. package/cli/commands/flow.ts +49 -49
  42. package/cli/commands/inspect/core.ts +12 -12
  43. package/cli/commands/inspect/count.ts +1 -1
  44. package/cli/commands/inspect/describe.ts +1 -1
  45. package/cli/commands/inspect/env.ts +2 -2
  46. package/cli/commands/inspect/find.ts +2 -2
  47. package/cli/commands/inspect/get-layout.ts +2 -2
  48. package/cli/commands/inspect/keyboard.ts +1 -1
  49. package/cli/commands/inspect/list.ts +4 -4
  50. package/cli/commands/inspect/memory.ts +1 -1
  51. package/cli/commands/inspect/resolve-target.ts +1 -1
  52. package/cli/commands/inspect/screens.ts +1 -1
  53. package/cli/commands/inspect/shared.ts +1 -1
  54. package/cli/commands/inspect/tree.ts +1 -1
  55. package/cli/commands/inspect/url.ts +1 -1
  56. package/cli/commands/inspect/wait-event.ts +5 -5
  57. package/cli/commands/inspect.ts +82 -82
  58. package/cli/commands/install-cli.ts +16 -16
  59. package/cli/commands/install-desktop.ts +26 -26
  60. package/cli/commands/login.ts +11 -11
  61. package/cli/commands/maestro-generate.ts +10 -10
  62. package/cli/commands/maestro.ts +173 -71
  63. package/cli/commands/mode.ts +4 -4
  64. package/cli/commands/no-bridge-hint.ts +12 -12
  65. package/cli/commands/perf.ts +11 -11
  66. package/cli/commands/permissions.ts +5 -5
  67. package/cli/commands/platform.ts +21 -21
  68. package/cli/commands/preview.ts +5 -5
  69. package/cli/commands/profile.ts +12 -12
  70. package/cli/commands/react.ts +15 -15
  71. package/cli/commands/record.ts +31 -31
  72. package/cli/commands/report-issue.ts +4 -4
  73. package/cli/commands/reset.ts +2 -2
  74. package/cli/commands/runtime.ts +23 -23
  75. package/cli/commands/screenshot-command.ts +2 -2
  76. package/cli/commands/screenshot-layers.ts +5 -5
  77. package/cli/commands/screenshot-mode.ts +7 -7
  78. package/cli/commands/screenshot.ts +24 -24
  79. package/cli/commands/screenshots-capture.ts +13 -13
  80. package/cli/commands/screenshots.ts +6 -6
  81. package/cli/commands/serve.ts +12 -12
  82. package/cli/commands/setup.ts +25 -25
  83. package/cli/commands/skills.ts +30 -23
  84. package/cli/commands/slides.ts +20 -20
  85. package/cli/commands/state.ts +2 -2
  86. package/cli/commands/storage.ts +3 -3
  87. package/cli/commands/telemetry.ts +2 -2
  88. package/cli/commands/test.ts +176 -18
  89. package/cli/commands/three-mode.ts +17 -17
  90. package/cli/commands/timeline.ts +12 -12
  91. package/cli/commands/upgrade.ts +11 -11
  92. package/cli/commands/upload.ts +26 -26
  93. package/cli/commands/version.ts +7 -7
  94. package/cli/commands/what-happened.ts +20 -20
  95. package/cli/current-sim.ts +3 -3
  96. package/cli/desktop-companion.ts +16 -16
  97. package/cli/drivers/electron.ts +4 -4
  98. package/cli/drivers/playwright-sim-host.ts +7 -7
  99. package/cli/drivers/playwright.ts +4 -4
  100. package/cli/drivers/registry.ts +1 -1
  101. package/cli/drivers/types.ts +5 -5
  102. package/cli/flow-export.ts +1 -1
  103. package/cli/flow-file.ts +5 -5
  104. package/cli/flow-session.ts +1 -1
  105. package/cli/help-core.ts +13 -13
  106. package/cli/hints.ts +14 -14
  107. package/cli/inspect-notice-state.ts +1 -1
  108. package/cli/internal-child.ts +2 -2
  109. package/cli/maestro-js.ts +4 -4
  110. package/cli/maestro-report.ts +4 -2
  111. package/cli/main.ts +14 -14
  112. package/cli/outbound-endpoints.ts +32 -30
  113. package/cli/parse-args.ts +7 -7
  114. package/cli/privacy.ts +1 -1
  115. package/cli/recording-access.ts +10 -10
  116. package/cli/resolve-assets.ts +2 -2
  117. package/cli/rnx-in-process.ts +5 -5
  118. package/cli/run-registry.ts +3 -2
  119. package/cli/run-rnx-core.ts +3 -3
  120. package/cli/run-rnx.ts +1 -1
  121. package/cli/runtime-notes.ts +1 -1
  122. package/cli/self-invocation.ts +1 -1
  123. package/cli/send-to-box.ts +1 -1
  124. package/cli/shell-init.ts +40 -40
  125. package/cli/ui.ts +1 -1
  126. package/cli/ws-bridge.ts +22 -22
  127. package/detox/config-loader.ts +1 -1
  128. package/detox/index.ts +28 -28
  129. package/detox/jest-preset.cjs +2 -2
  130. package/detox/navigation.ts +2 -2
  131. package/dist-lib/agent-daemon-client.cjs +12 -12
  132. package/dist-lib/agent-events.cjs +1 -1
  133. package/dist-lib/agent-identity.cjs +2 -2
  134. package/dist-lib/agent-sessions.cjs +12 -12
  135. package/dist-lib/attached-projects.cjs +4 -4
  136. package/dist-lib/auth/shared-session.cjs +4 -4
  137. package/dist-lib/backend-origin.cjs +1 -1
  138. package/dist-lib/beta.cjs +1 -1
  139. package/dist-lib/beta.mjs +1 -1
  140. package/dist-lib/bridge-constants.cjs +4 -4
  141. package/dist-lib/bridge-contract-input.cjs +1 -1
  142. package/dist-lib/bridge-contract-input.mjs +1 -1
  143. package/dist-lib/bridge-contract.cjs +2 -2
  144. package/dist-lib/bridge-contract.mjs +2 -2
  145. package/dist-lib/capture-contract.cjs +1 -1
  146. package/dist-lib/capture-contract.mjs +1 -1
  147. package/dist-lib/cli-constants.cjs +1 -1
  148. package/dist-lib/cloud-contract.cjs +1 -1
  149. package/dist-lib/cloud-contract.mjs +1 -1
  150. package/dist-lib/cloud-test-args.cjs +116 -0
  151. package/dist-lib/cloud-test-args.mjs +86 -0
  152. package/dist-lib/cloud.cjs +42 -42
  153. package/dist-lib/cloud.mjs +42 -42
  154. package/dist-lib/config.cjs +2 -2
  155. package/dist-lib/detox/index.cjs +29 -29
  156. package/dist-lib/detox/jest-preset.cjs +2 -2
  157. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  158. package/dist-lib/home-paths.cjs +12 -12
  159. package/dist-lib/host/bridge-host.cjs +87 -77
  160. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  161. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  162. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  163. package/dist-lib/host/replacement-module-handler.cjs +6 -6
  164. package/dist-lib/host/websocket-proxy.cjs +1 -1
  165. package/dist-lib/index.cjs +63 -73
  166. package/dist-lib/jump-to-source-babel.cjs +1 -1
  167. package/dist-lib/jump-to-source-native.cjs +1 -1
  168. package/dist-lib/menu.cjs +5 -5
  169. package/dist-lib/menu.mjs +5 -5
  170. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  171. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  172. package/dist-lib/metro-production-bundle.cjs +1 -1
  173. package/dist-lib/metro-production-bundle.mjs +1 -1
  174. package/dist-lib/metro.cjs +15 -15
  175. package/dist-lib/profiles.cjs +4 -4
  176. package/dist-lib/public-brand.cjs +3 -3
  177. package/dist-lib/react-native-host-modules.cjs +1 -1
  178. package/dist-lib/react-native-host-modules.mjs +1 -1
  179. package/dist-lib/render-mode.cjs +1 -1
  180. package/dist-lib/scripts/dev-server-scanner.cjs +2 -1
  181. package/dist-lib/sdk.cjs +9 -9
  182. package/dist-lib/sdk.mjs +9 -9
  183. package/dist-lib/skills.cjs +534 -520
  184. package/dist-lib/swift-compile.cjs +372 -0
  185. package/dist-lib/swift-compile.mjs +339 -0
  186. package/dist-lib/swift-package.cjs +78 -0
  187. package/dist-lib/swift-package.mjs +52 -0
  188. package/dist-lib/swift-submit.cjs +1 -1
  189. package/dist-lib/swift-submit.mjs +1 -1
  190. package/dist-lib/swift.cjs +1 -1
  191. package/dist-lib/vite.cjs +479 -161
  192. package/package.json +22 -1
  193. package/scripts/dev-server-scanner.ts +4 -3
  194. package/skills/contrast/SKILL.md +75 -75
  195. package/skills/{rnx-debug → peach-debug}/SKILL.md +90 -90
  196. package/skills/{rnx-perf → peach-perf}/SKILL.md +13 -13
  197. package/skills/{rnx-setup → peach-setup}/SKILL.md +60 -60
  198. package/skills/{rnx-test → peach-test}/SKILL.md +47 -47
  199. package/skills/{rnx-visual → peach-visual}/SKILL.md +26 -26
  200. package/src/agent-daemon-client.ts +5 -5
  201. package/src/agent-events.ts +1 -1
  202. package/src/agent-identity.ts +1 -1
  203. package/src/agent-sessions.ts +13 -13
  204. package/src/attached-projects.ts +1 -1
  205. package/src/auth/shared-session.ts +1 -1
  206. package/src/bridge-constants.ts +3 -3
  207. package/src/bridge-contract-input.ts +1 -1
  208. package/src/bridge-contract.ts +3 -3
  209. package/src/cli-version.ts +6 -6
  210. package/src/cloud-test-args.ts +100 -0
  211. package/src/cloud.ts +40 -40
  212. package/src/config.ts +1 -1
  213. package/src/connect.ts +6 -6
  214. package/src/dev-server-open.ts +8 -8
  215. package/src/disk-cleanup.ts +3 -3
  216. package/src/home-paths.ts +26 -26
  217. package/src/host/agent-host.ts +3 -3
  218. package/src/host/bridge-host.ts +33 -33
  219. package/src/host/fetch-proxy-handler.ts +1 -1
  220. package/src/host/plane-host.ts +2 -2
  221. package/src/host/replacement-module-handler.ts +2 -2
  222. package/src/menu.ts +2 -2
  223. package/src/metro-plugin.ts +7 -7
  224. package/src/metro-production-bundle.ts +2 -2
  225. package/src/native-dev-bundle-url.ts +2 -2
  226. package/src/playwright-host-log.ts +1 -1
  227. package/src/public-brand.ts +2 -2
  228. package/src/react-native-host-modules.ts +1 -1
  229. package/src/rolldown-plugin-swift.ts +11 -10
  230. package/src/runtime-assets.ts +11 -11
  231. package/src/runtime-delivery.ts +6 -6
  232. package/src/screenshots/orchestrate.ts +1 -1
  233. package/src/screenshots/org-deck.ts +1 -1
  234. package/src/screenshots/schema.ts +2 -2
  235. package/src/sim-client.ts +1 -1
  236. package/src/skills/builtin/compat-check.ts +2 -2
  237. package/src/swift-compile.ts +359 -0
  238. package/src/swift-package.ts +69 -0
  239. package/src/swift-submit.ts +3 -2
  240. package/src/vite-plugin-one.ts +8 -8
  241. package/src/vite-plugin-swift.ts +106 -110
  242. package/src/vite-plugin.ts +6 -19
@@ -0,0 +1,359 @@
1
+ import { execFile } from 'node:child_process'
2
+ import { createHash } from 'node:crypto'
3
+ import {
4
+ existsSync,
5
+ mkdirSync,
6
+ readFileSync,
7
+ readdirSync,
8
+ realpathSync,
9
+ rmSync,
10
+ statSync,
11
+ writeFileSync,
12
+ } from 'node:fs'
13
+ import path from 'node:path'
14
+ import { promisify } from 'node:util'
15
+ import { swiftManifestTargets, type SwiftPackageTarget } from './swift-package'
16
+ import type { SwiftImageUpload } from './swift-submit'
17
+
18
+ const run = promisify(execFile)
19
+ const WASM_TRIPLE = 'wasm32-unknown-wasip1'
20
+
21
+ export interface SwiftCompileInput {
22
+ files: Record<string, string>
23
+ resources?: Record<string, string>
24
+ images?: Record<string, SwiftImageUpload>
25
+ entry: string
26
+ }
27
+
28
+ export interface SwiftCompileOptions {
29
+ buildRoot: string
30
+ moduleRoot: string
31
+ release: string
32
+ toolchainBin: string
33
+ timeoutMs?: number
34
+ }
35
+
36
+ export interface SwiftCompileArtifact {
37
+ hash: string
38
+ bytes: Buffer
39
+ }
40
+
41
+ export interface SwiftProjectLayout {
42
+ root: string
43
+ sources: string
44
+ multi: boolean
45
+ }
46
+
47
+ export function swiftModuleInputFiles(root: string): string[] {
48
+ const files: string[] = ['Package.swift', 'scripts/contrast-app-Package.swift']
49
+ const walk = (dir: string) => {
50
+ for (const entry of readdirSync(path.join(root, dir), { withFileTypes: true })) {
51
+ const relative = `${dir}/${entry.name}`
52
+ if (entry.isDirectory()) walk(relative)
53
+ else if (entry.name.endsWith('.swift')) files.push(relative)
54
+ }
55
+ }
56
+ walk('Sources')
57
+ return files.sort()
58
+ }
59
+
60
+ const linkedModuleDigests = new Map<string, { stamp: string; digest: Buffer }>()
61
+
62
+ export function clearLinkedModuleDigests(root?: string): void {
63
+ if (root === undefined) linkedModuleDigests.clear()
64
+ else linkedModuleDigests.delete(root)
65
+ }
66
+
67
+ function linkedModuleDigest(root: string): Buffer {
68
+ const files = swiftModuleInputFiles(root)
69
+ const stampHash = createHash('sha256')
70
+ for (const file of files) {
71
+ const stat = statSync(path.join(root, file), { bigint: true })
72
+ stampHash.update(file)
73
+ stampHash.update(
74
+ `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}\n`,
75
+ )
76
+ }
77
+ const stamp = stampHash.digest('hex')
78
+ const cached = linkedModuleDigests.get(root)
79
+ if (cached?.stamp === stamp) return cached.digest
80
+ const hash = createHash('sha256')
81
+ for (const file of files) {
82
+ hash.update(createHash('sha256').update(file).digest())
83
+ hash.update(
84
+ createHash('sha256')
85
+ .update(readFileSync(path.join(root, file)))
86
+ .digest(),
87
+ )
88
+ }
89
+ const digest = hash.digest()
90
+ linkedModuleDigests.set(root, { stamp, digest })
91
+ return digest
92
+ }
93
+
94
+ export function compileTargets(input: SwiftCompileInput): SwiftPackageTarget[] {
95
+ const manifest = input.files['Package.swift']
96
+ return manifest === undefined ? [] : swiftManifestTargets(manifest)
97
+ }
98
+
99
+ function swiftSiblingDependencies(
100
+ files: Record<string, string>,
101
+ targets: SwiftPackageTarget[],
102
+ ): Map<string, string[]> {
103
+ const names = new Set(targets.map((target) => target.name))
104
+ const siblings = new Map<string, string[]>()
105
+ for (const target of targets) {
106
+ const prefix = `${target.path}/`
107
+ const deps = new Set<string>()
108
+ for (const [file, contents] of Object.entries(files)) {
109
+ if (file === 'Package.swift' || !file.startsWith(prefix)) continue
110
+ for (const match of contents.matchAll(/^\s*import\s+([A-Za-z_][A-Za-z0-9_]*)/gm)) {
111
+ if (names.has(match[1]) && match[1] !== target.name) deps.add(match[1])
112
+ }
113
+ }
114
+ siblings.set(target.name, [...deps].sort())
115
+ }
116
+ return siblings
117
+ }
118
+
119
+ /** bump with any change to the emitted multi-target manifest text. */
120
+ export const MULTI_MANIFEST_VERSION = 1
121
+
122
+ export function swiftArtifactHash(
123
+ input: SwiftCompileInput,
124
+ release: string,
125
+ moduleRoot: string,
126
+ ): string {
127
+ const hash = createHash('sha256')
128
+ hash.update(`swift-compile/v1\n${release}\n`)
129
+ hash.update(linkedModuleDigest(moduleRoot))
130
+ for (const file of Object.keys(input.files).sort()) {
131
+ hash.update(createHash('sha256').update(file).digest())
132
+ hash.update(createHash('sha256').update(input.files[file]).digest())
133
+ }
134
+ for (const base of Object.keys(input.resources ?? {}).sort()) {
135
+ hash.update(createHash('sha256').update(base).digest())
136
+ hash.update(
137
+ createHash('sha256')
138
+ .update(input.resources?.[base] ?? '')
139
+ .digest(),
140
+ )
141
+ }
142
+ for (const name of Object.keys(input.images ?? {}).sort()) {
143
+ const image = input.images?.[name]
144
+ if (!image) continue
145
+ hash.update(createHash('sha256').update(name).digest())
146
+ hash.update(createHash('sha256').update(image.file).digest())
147
+ hash.update(createHash('sha256').update(image.data).digest())
148
+ }
149
+ const targets = compileTargets(input)
150
+ if (targets.length > 0) {
151
+ hash.update(`swift-multi/v${MULTI_MANIFEST_VERSION}\n`)
152
+ hash.update(
153
+ JSON.stringify({
154
+ targets,
155
+ siblings: [...swiftSiblingDependencies(input.files, targets)].sort((a, b) =>
156
+ String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0,
157
+ ),
158
+ }),
159
+ )
160
+ }
161
+ return hash.digest('hex')
162
+ }
163
+
164
+ export function emitSwiftPackageManifest(args: {
165
+ targets: SwiftPackageTarget[]
166
+ siblings: Map<string, string[]>
167
+ moduleRoot: string
168
+ }): string {
169
+ const { targets, siblings, moduleRoot } = args
170
+ const executable = targets.find((target) => target.executable)
171
+ if (!executable) throw new Error('swift-compile: no executable target to emit')
172
+ const swiftSettings =
173
+ 'swiftSettings: [.swiftLanguageMode(.v5), .unsafeFlags(["-Osize"])]'
174
+ const stanza = (target: SwiftPackageTarget): string => {
175
+ const deps = [
176
+ '.product(name: "SwiftUI", package: "sootsim-swift")',
177
+ ...(siblings.get(target.name) ?? []).map((sibling) => `"${sibling}"`),
178
+ ].join(', ')
179
+ const kind = target.executable ? '.executableTarget' : '.target'
180
+ const link = target.executable
181
+ ? `,
182
+ linkerSettings: [
183
+ .unsafeFlags([
184
+ "-Xclang-linker", "-mexec-model=reactor",
185
+ "-Xlinker", "--export-if-defined=__main_argc_argv",
186
+ ]),
187
+ .unsafeFlags(["-Xlinker", "--strip-all"], .when(configuration: .release)),
188
+ ]`
189
+ : ''
190
+ return [
191
+ ` ${kind}(`,
192
+ ` name: "${target.name}",`,
193
+ ` dependencies: [${deps}],`,
194
+ ` path: "${target.path}",`,
195
+ ` ${swiftSettings}${link}`,
196
+ ` ),`,
197
+ ].join('\n')
198
+ }
199
+ return [
200
+ '// swift-tools-version: 6.0',
201
+ 'import PackageDescription',
202
+ '',
203
+ '// generated by swift-compile from the project package partition.',
204
+ 'let package = Package(',
205
+ ' name: "app",',
206
+ ` products: [.executable(name: "${executable.name}", targets: ["${executable.name}"])],`,
207
+ ` dependencies: [.package(path: "${moduleRoot}")],`,
208
+ ' targets: [',
209
+ ...targets.map(stanza),
210
+ ' ]',
211
+ ')',
212
+ '',
213
+ ].join('\n')
214
+ }
215
+
216
+ export function writeSwiftProject(
217
+ layout: SwiftProjectLayout,
218
+ input: SwiftCompileInput,
219
+ moduleRoot: string,
220
+ ): void {
221
+ const targets = compileTargets(input)
222
+ if (targets.length === 0) {
223
+ rmSync(layout.sources, { recursive: true, force: true })
224
+ for (const [file, contents] of Object.entries(input.files)) {
225
+ const target = path.join(layout.sources, file)
226
+ mkdirSync(path.dirname(target), { recursive: true })
227
+ writeFileSync(target, contents, 'utf8')
228
+ }
229
+ const manifest = readFileSync(
230
+ path.join(moduleRoot, 'scripts/contrast-app-Package.swift'),
231
+ 'utf8',
232
+ ).replaceAll('__SOOTSIM_SWIFT_PACKAGE__', moduleRoot)
233
+ writeFileSync(path.join(layout.root, 'Package.swift'), manifest, 'utf8')
234
+ return
235
+ }
236
+ mkdirSync(layout.root, { recursive: true })
237
+ for (const child of readdirSync(layout.root)) {
238
+ if (child === '.build') continue
239
+ rmSync(path.join(layout.root, child), { recursive: true, force: true })
240
+ }
241
+ for (const [file, contents] of Object.entries(input.files)) {
242
+ if (file === 'Package.swift') continue
243
+ const target = path.join(layout.root, file)
244
+ mkdirSync(path.dirname(target), { recursive: true })
245
+ writeFileSync(target, contents, 'utf8')
246
+ }
247
+ writeFileSync(
248
+ path.join(layout.root, 'Package.swift'),
249
+ emitSwiftPackageManifest({
250
+ targets,
251
+ siblings: swiftSiblingDependencies(input.files, targets),
252
+ moduleRoot,
253
+ }),
254
+ 'utf8',
255
+ )
256
+ }
257
+
258
+ function streamText(value: unknown): string {
259
+ if (typeof value === 'string') return value.trim()
260
+ if (Buffer.isBuffer(value)) return value.toString('utf8').trim()
261
+ return ''
262
+ }
263
+
264
+ function outputOf(error: unknown): string {
265
+ if (error && typeof error === 'object') {
266
+ const streams = [
267
+ streamText(Reflect.get(error, 'stdout')),
268
+ streamText(Reflect.get(error, 'stderr')),
269
+ ].filter(Boolean)
270
+ if (streams.length > 0) return streams.join('\n')
271
+ }
272
+ return error instanceof Error ? error.message : String(error)
273
+ }
274
+
275
+ function builtArtifact(layout: SwiftProjectLayout): string {
276
+ const binDir = path.join(layout.root, '.build', WASM_TRIPLE, 'release')
277
+ const artifacts = existsSync(binDir)
278
+ ? readdirSync(binDir)
279
+ .filter((name) => name.endsWith('.wasm'))
280
+ .sort()
281
+ : []
282
+ if (artifacts.length !== 1) {
283
+ throw new Error(
284
+ `swift-compile: the build produced ${artifacts.length} wasm artifacts; exactly one executable is the app`,
285
+ )
286
+ }
287
+ return path.join(binDir, artifacts[0])
288
+ }
289
+
290
+ function projectDiagnostic(output: string, layout: SwiftProjectLayout): string {
291
+ const base = layout.multi ? layout.root : layout.sources
292
+ const prefix = `${base}${path.sep}`
293
+ return output
294
+ .split('\n')
295
+ .map((line) => (line.startsWith(prefix) ? line.slice(prefix.length) : line))
296
+ .join('\n')
297
+ .trim()
298
+ }
299
+
300
+ const ROOT_LOCKS = Symbol.for('peachsim.swiftCompile.rootLocks')
301
+ const host = globalThis as typeof globalThis & {
302
+ [ROOT_LOCKS]?: Map<string, Promise<unknown>>
303
+ }
304
+ const rootLocks = (host[ROOT_LOCKS] ??= new Map())
305
+
306
+ function serialize<T>(root: string, work: () => Promise<T>): Promise<T> {
307
+ const current = rootLocks.get(root) ?? Promise.resolve()
308
+ const next = current.then(work, work)
309
+ rootLocks.set(
310
+ root,
311
+ next.catch(() => {}),
312
+ )
313
+ return next
314
+ }
315
+
316
+ /** the one local compiler used by the route host and Vite host. */
317
+ export function compileSwiftProjectUnit(
318
+ input: SwiftCompileInput,
319
+ options: SwiftCompileOptions,
320
+ ): Promise<SwiftCompileArtifact> {
321
+ mkdirSync(options.buildRoot, { recursive: true })
322
+ const root = realpathSync(options.buildRoot)
323
+ const moduleRoot = path.resolve(options.moduleRoot)
324
+ const layout: SwiftProjectLayout = {
325
+ root,
326
+ sources: path.join(root, 'Sources', 'App'),
327
+ multi: compileTargets(input).length > 0,
328
+ }
329
+ const hash = swiftArtifactHash(input, options.release, moduleRoot)
330
+ return serialize(root, async () => {
331
+ writeSwiftProject(layout, input, moduleRoot)
332
+ const swift = path.join(options.toolchainBin, 'swift')
333
+ if (!existsSync(swift)) {
334
+ throw new Error(
335
+ `swift-compile: no swift.org toolchain at ${options.toolchainBin}; install ${options.release} and its matching wasm sdk`,
336
+ )
337
+ }
338
+ try {
339
+ await run(
340
+ swift,
341
+ [
342
+ 'build',
343
+ '--package-path',
344
+ root,
345
+ '--swift-sdk',
346
+ `${options.release}_wasm`,
347
+ '-c',
348
+ 'release',
349
+ '-Xswiftc',
350
+ '-Osize',
351
+ ],
352
+ { maxBuffer: 64 * 1024 * 1024, timeout: options.timeoutMs },
353
+ )
354
+ } catch (error) {
355
+ throw new Error(projectDiagnostic(outputOf(error), layout), { cause: error })
356
+ }
357
+ return { hash, bytes: readFileSync(builtArtifact(layout)) }
358
+ })
359
+ }
@@ -0,0 +1,69 @@
1
+ /** one SwiftPM target a sent root manifest declares. */
2
+ export interface SwiftPackageTarget {
3
+ /** the target name, as `import <name>` spells it. */
4
+ name: string
5
+ /** project-relative directory holding the target's sources. */
6
+ path: string
7
+ /** true for `.executableTarget`: the one that links the wasm artifact. */
8
+ executable: boolean
9
+ }
10
+
11
+ /** whether a project-relative key is a SwiftPM manifest, not a source. */
12
+ export function isSwiftManifestFile(file: string): boolean {
13
+ const segments = file.split('/')
14
+ return segments[segments.length - 1] === 'Package.swift'
15
+ }
16
+
17
+ /**
18
+ * the library and executable targets a sent root manifest declares, in order.
19
+ *
20
+ * A manifest is Swift code, not data, so this is a scanner, not a parser:
21
+ * each `.target(` / `.executableTarget(` call is matched to its balanced
22
+ * close paren (string-aware, so a paren inside a literal cannot end it
23
+ * early), and `name:` / `path:` are read from the call's own depth only, so
24
+ * a nested `.product(name: "SwiftUI", ...)` inside dependencies never wins.
25
+ */
26
+ export function swiftManifestTargets(manifest: string): SwiftPackageTarget[] {
27
+ const code = manifest.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, '')
28
+ const targets: SwiftPackageTarget[] = []
29
+ const pattern = /\.(executableTarget|target)\s*\(/g
30
+ let match: RegExpExecArray | null
31
+ while ((match = pattern.exec(code)) !== null) {
32
+ const executable = match[1] === 'executableTarget'
33
+ let depth = 1
34
+ let i = pattern.lastIndex
35
+ let inString = false
36
+ let escaped = false
37
+ let flat = ''
38
+ while (i < code.length && depth > 0) {
39
+ const ch = code[i]
40
+ if (inString) {
41
+ if (escaped) escaped = false
42
+ else if (ch === '\\') escaped = true
43
+ else if (ch === '"') inString = false
44
+ if (depth === 1) flat += ch
45
+ } else if (ch === '"') {
46
+ inString = true
47
+ if (depth === 1) flat += ch
48
+ } else if (ch === '(') {
49
+ depth++
50
+ if (depth === 1) flat += ch
51
+ } else if (ch === ')') {
52
+ depth--
53
+ if (depth >= 1) flat += ch
54
+ } else if (depth === 1) {
55
+ flat += ch
56
+ }
57
+ i++
58
+ }
59
+ const name = /\bname\s*:\s*"([^"]+)"/.exec(flat)?.[1]
60
+ if (!name) continue
61
+ const rawPath = /\bpath\s*:\s*"([^"]*)"/.exec(flat)?.[1]
62
+ const targetPath =
63
+ rawPath == null || rawPath === ''
64
+ ? `Sources/${name}`
65
+ : rawPath.replace(/^\.\//, '').replace(/\/+$/, '')
66
+ targets.push({ name, path: targetPath, executable })
67
+ }
68
+ return targets
69
+ }
@@ -7,8 +7,9 @@
7
7
  * its own origin's `/api/swift-compile`), the node bundle builder used by
8
8
  * the test-drive CLI and the deploy publisher (posts an absolute endpoint),
9
9
  * and the One/rolldown `.swift` transform (same, with a `ccdn_` credential).
10
- * A caller never compiles in-process: the container is the one compiler, and
11
- * no bundler is that instance.
10
+ * Remote callers never compile in-process: the container is the compiler for
11
+ * this path. The local Vite host uses the same project-unit compiler directly
12
+ * and does not pass through this HTTP adapter.
12
13
  *
13
14
  * It lives here (rather than in the app's `src/bundler/`) because the One
14
15
  * transform ships in this package and cannot import app code; the app's own
@@ -1,5 +1,5 @@
1
1
  // sootsim plugin for One/Vite — serves the installed sootsim runtime at /__soot/
2
- // from ~/.rnx/runtimes/<version>. version-owned localhost origins remain
2
+ // from ~/.peach/runtimes/<version>. version-owned localhost origins remain
3
3
  // bound to that directory across an active-runtime update.
4
4
  //
5
5
  // usage in vite.config.ts:
@@ -35,8 +35,8 @@ export interface RnxPluginOptions {
35
35
  prefix?: string
36
36
  // disable sootsim
37
37
  enabled?: boolean
38
- // launch the sootsim simulator loading this server's bundle once the dev
39
- // server is listening. skipped when CI or RNX_NO_OPEN is set.
38
+ // launch the peach simulator loading this server's bundle once the dev
39
+ // server is listening. skipped when CI or PEACH_NO_OPEN is set.
40
40
  open?: RnxDesktopOpenOption
41
41
  }
42
42
 
@@ -96,7 +96,7 @@ export function rnxPlugin(options: RnxPluginOptions = {}): Plugin {
96
96
  : DEFAULT_SOOTSIM_BRIDGE_PORT
97
97
  // inject the bridge port + a default bundle (only when the URL has none,
98
98
  // so the CLI's resolved bundle wins), and PRESERVE the existing query.
99
- // preserving the query is essential: `rnx open` stamps the open URL
99
+ // preserving the query is essential: `peach open` stamps the open URL
100
100
  // with `inspectOpen=<token>` and matches the registered sim by that
101
101
  // token (waitForSimMatch). the old code forced `?bundle=` only and
102
102
  // stripped inspectOpen, so the sim registered but never matched and
@@ -134,7 +134,7 @@ export function rnxPlugin(options: RnxPluginOptions = {}): Plugin {
134
134
  // bundle. dev-bundle-resolution's resolvePortViaServerScan fetches
135
135
  // `/__server-scan` RELATIVE to the shell origin; when the shell is loaded
136
136
  // from this target origin, that hits here. without it the request SPA-
137
- // falls-back to 404 and the bundle never resolves (rnx open hangs).
137
+ // falls-back to 404 and the bundle never resolves (peach open hangs).
138
138
  // we describe only this server (the shell drives THIS app) — no localhost-
139
139
  // wide scan, so the published plugin pulls in no shell-only scanner
140
140
  // internals. shape matches DiscoveredServer enough for the resolver.
@@ -198,7 +198,7 @@ export function rnxPlugin(options: RnxPluginOptions = {}): Plugin {
198
198
  }
199
199
  }
200
200
 
201
- // serve the sootsim HTML shell — inject bundle URL + bridge port
201
+ // serve the peach HTML shell — inject bundle URL + bridge port
202
202
  if (pathname === prefix || pathname === prefix + '/') {
203
203
  res.setHeader('content-type', 'text/html')
204
204
  res.end(buildShellHtml(runtimeRoot))
@@ -230,9 +230,9 @@ export function rnxPlugin(options: RnxPluginOptions = {}): Plugin {
230
230
 
231
231
  const port = configuredPort
232
232
  const sootsimUrl = `http://localhost:${port}${prefix}/`
233
- console.log(`[rnx] serving at ${sootsimUrl}`)
233
+ console.log(`[peach] serving at ${sootsimUrl}`)
234
234
 
235
- if (appName && !process.env.CI && !process.env.RNX_NO_OPEN) {
235
+ if (appName && !process.env.CI && !process.env.PEACH_NO_OPEN) {
236
236
  server.httpServer?.once('listening', () => {
237
237
  const openPort = listeningPort()
238
238
  launchRnxDesktop(