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.
- package/LICENSE +1 -1
- package/README.md +108 -108
- package/cli/app-config.ts +1 -1
- package/cli/app-fonts.ts +4 -4
- package/cli/auth.ts +11 -11
- package/cli/bin.ts +4 -4
- package/cli/bridge-flow-runner.ts +28 -20
- package/cli/browser-evals.ts +2 -2
- package/cli/cli-update.ts +6 -6
- package/cli/cloud-client.ts +16 -16
- package/cli/cloud-dispatch.ts +3 -2
- package/cli/cloud-session.ts +12 -12
- package/cli/cloud-test-result-files.ts +97 -0
- package/cli/cloud-test-tunnel.ts +4 -4
- package/cli/command-registry.ts +7 -7
- package/cli/commands/agent-wrapper.ts +7 -7
- package/cli/commands/agent.ts +16 -16
- package/cli/commands/app-fonts.ts +3 -3
- package/cli/commands/assert.ts +10 -10
- package/cli/commands/auth.ts +5 -5
- package/cli/commands/box/checkout-plane.ts +1 -1
- package/cli/commands/box/plane-share.ts +2 -2
- package/cli/commands/box/rnx-command.ts +5 -5
- package/cli/commands/box.ts +38 -38
- package/cli/commands/camera.ts +13 -13
- package/cli/commands/cleanup.ts +13 -13
- package/cli/commands/compat.ts +7 -7
- package/cli/commands/config.ts +5 -5
- package/cli/commands/control.ts +63 -63
- package/cli/commands/cpu-profile.ts +11 -11
- package/cli/commands/daemon-mac-app.ts +10 -10
- package/cli/commands/daemon.ts +29 -27
- package/cli/commands/debug.ts +35 -35
- package/cli/commands/desktop.ts +3 -3
- package/cli/commands/detox.ts +33 -33
- package/cli/commands/device.ts +9 -9
- package/cli/commands/diagnose.ts +17 -17
- package/cli/commands/do-chain.ts +4 -4
- package/cli/commands/electron.ts +9 -9
- package/cli/commands/film.ts +9 -9
- package/cli/commands/flow.ts +49 -49
- package/cli/commands/inspect/core.ts +12 -12
- package/cli/commands/inspect/count.ts +1 -1
- package/cli/commands/inspect/describe.ts +1 -1
- package/cli/commands/inspect/env.ts +2 -2
- package/cli/commands/inspect/find.ts +2 -2
- package/cli/commands/inspect/get-layout.ts +2 -2
- package/cli/commands/inspect/keyboard.ts +1 -1
- package/cli/commands/inspect/list.ts +4 -4
- package/cli/commands/inspect/memory.ts +1 -1
- package/cli/commands/inspect/resolve-target.ts +1 -1
- package/cli/commands/inspect/screens.ts +1 -1
- package/cli/commands/inspect/shared.ts +1 -1
- package/cli/commands/inspect/tree.ts +1 -1
- package/cli/commands/inspect/url.ts +1 -1
- package/cli/commands/inspect/wait-event.ts +5 -5
- package/cli/commands/inspect.ts +82 -82
- package/cli/commands/install-cli.ts +16 -16
- package/cli/commands/install-desktop.ts +26 -26
- package/cli/commands/login.ts +11 -11
- package/cli/commands/maestro-generate.ts +10 -10
- package/cli/commands/maestro.ts +173 -71
- package/cli/commands/mode.ts +4 -4
- package/cli/commands/no-bridge-hint.ts +12 -12
- package/cli/commands/perf.ts +11 -11
- package/cli/commands/permissions.ts +5 -5
- package/cli/commands/platform.ts +21 -21
- package/cli/commands/preview.ts +5 -5
- package/cli/commands/profile.ts +12 -12
- package/cli/commands/react.ts +15 -15
- package/cli/commands/record.ts +31 -31
- package/cli/commands/report-issue.ts +4 -4
- package/cli/commands/reset.ts +2 -2
- package/cli/commands/runtime.ts +23 -23
- package/cli/commands/screenshot-command.ts +2 -2
- package/cli/commands/screenshot-layers.ts +5 -5
- package/cli/commands/screenshot-mode.ts +7 -7
- package/cli/commands/screenshot.ts +24 -24
- package/cli/commands/screenshots-capture.ts +13 -13
- package/cli/commands/screenshots.ts +6 -6
- package/cli/commands/serve.ts +12 -12
- package/cli/commands/setup.ts +25 -25
- package/cli/commands/skills.ts +30 -23
- package/cli/commands/slides.ts +20 -20
- package/cli/commands/state.ts +2 -2
- package/cli/commands/storage.ts +3 -3
- package/cli/commands/telemetry.ts +2 -2
- package/cli/commands/test.ts +176 -18
- package/cli/commands/three-mode.ts +17 -17
- package/cli/commands/timeline.ts +12 -12
- package/cli/commands/upgrade.ts +11 -11
- package/cli/commands/upload.ts +26 -26
- package/cli/commands/version.ts +7 -7
- package/cli/commands/what-happened.ts +20 -20
- package/cli/current-sim.ts +3 -3
- package/cli/desktop-companion.ts +16 -16
- package/cli/drivers/electron.ts +4 -4
- package/cli/drivers/playwright-sim-host.ts +7 -7
- package/cli/drivers/playwright.ts +4 -4
- package/cli/drivers/registry.ts +1 -1
- package/cli/drivers/types.ts +5 -5
- package/cli/flow-export.ts +1 -1
- package/cli/flow-file.ts +5 -5
- package/cli/flow-session.ts +1 -1
- package/cli/help-core.ts +13 -13
- package/cli/hints.ts +14 -14
- package/cli/inspect-notice-state.ts +1 -1
- package/cli/internal-child.ts +2 -2
- package/cli/maestro-js.ts +4 -4
- package/cli/maestro-report.ts +4 -2
- package/cli/main.ts +14 -14
- package/cli/outbound-endpoints.ts +32 -30
- package/cli/parse-args.ts +7 -7
- package/cli/privacy.ts +1 -1
- package/cli/recording-access.ts +10 -10
- package/cli/resolve-assets.ts +2 -2
- package/cli/rnx-in-process.ts +5 -5
- package/cli/run-registry.ts +3 -2
- package/cli/run-rnx-core.ts +3 -3
- package/cli/run-rnx.ts +1 -1
- package/cli/runtime-notes.ts +1 -1
- package/cli/self-invocation.ts +1 -1
- package/cli/send-to-box.ts +1 -1
- package/cli/shell-init.ts +40 -40
- package/cli/ui.ts +1 -1
- package/cli/ws-bridge.ts +22 -22
- package/detox/config-loader.ts +1 -1
- package/detox/index.ts +28 -28
- package/detox/jest-preset.cjs +2 -2
- package/detox/navigation.ts +2 -2
- package/dist-lib/agent-daemon-client.cjs +12 -12
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +2 -2
- package/dist-lib/agent-sessions.cjs +12 -12
- package/dist-lib/attached-projects.cjs +4 -4
- package/dist-lib/auth/shared-session.cjs +4 -4
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +4 -4
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +2 -2
- package/dist-lib/bridge-contract.mjs +2 -2
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +116 -0
- package/dist-lib/cloud-test-args.mjs +86 -0
- package/dist-lib/cloud.cjs +42 -42
- package/dist-lib/cloud.mjs +42 -42
- package/dist-lib/config.cjs +2 -2
- package/dist-lib/detox/index.cjs +29 -29
- package/dist-lib/detox/jest-preset.cjs +2 -2
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +12 -12
- package/dist-lib/host/bridge-host.cjs +87 -77
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +6 -6
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +63 -73
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/menu.cjs +5 -5
- package/dist-lib/menu.mjs +5 -5
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +15 -15
- package/dist-lib/profiles.cjs +4 -4
- package/dist-lib/public-brand.cjs +3 -3
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +2 -1
- package/dist-lib/sdk.cjs +9 -9
- package/dist-lib/sdk.mjs +9 -9
- package/dist-lib/skills.cjs +534 -520
- package/dist-lib/swift-compile.cjs +372 -0
- package/dist-lib/swift-compile.mjs +339 -0
- package/dist-lib/swift-package.cjs +78 -0
- package/dist-lib/swift-package.mjs +52 -0
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite.cjs +479 -161
- package/package.json +22 -1
- package/scripts/dev-server-scanner.ts +4 -3
- package/skills/contrast/SKILL.md +75 -75
- package/skills/{rnx-debug → peach-debug}/SKILL.md +90 -90
- package/skills/{rnx-perf → peach-perf}/SKILL.md +13 -13
- package/skills/{rnx-setup → peach-setup}/SKILL.md +60 -60
- package/skills/{rnx-test → peach-test}/SKILL.md +47 -47
- package/skills/{rnx-visual → peach-visual}/SKILL.md +26 -26
- package/src/agent-daemon-client.ts +5 -5
- package/src/agent-events.ts +1 -1
- package/src/agent-identity.ts +1 -1
- package/src/agent-sessions.ts +13 -13
- package/src/attached-projects.ts +1 -1
- package/src/auth/shared-session.ts +1 -1
- package/src/bridge-constants.ts +3 -3
- package/src/bridge-contract-input.ts +1 -1
- package/src/bridge-contract.ts +3 -3
- package/src/cli-version.ts +6 -6
- package/src/cloud-test-args.ts +100 -0
- package/src/cloud.ts +40 -40
- package/src/config.ts +1 -1
- package/src/connect.ts +6 -6
- package/src/dev-server-open.ts +8 -8
- package/src/disk-cleanup.ts +3 -3
- package/src/home-paths.ts +26 -26
- package/src/host/agent-host.ts +3 -3
- package/src/host/bridge-host.ts +33 -33
- package/src/host/fetch-proxy-handler.ts +1 -1
- package/src/host/plane-host.ts +2 -2
- package/src/host/replacement-module-handler.ts +2 -2
- package/src/menu.ts +2 -2
- package/src/metro-plugin.ts +7 -7
- package/src/metro-production-bundle.ts +2 -2
- package/src/native-dev-bundle-url.ts +2 -2
- package/src/playwright-host-log.ts +1 -1
- package/src/public-brand.ts +2 -2
- package/src/react-native-host-modules.ts +1 -1
- package/src/rolldown-plugin-swift.ts +11 -10
- package/src/runtime-assets.ts +11 -11
- package/src/runtime-delivery.ts +6 -6
- package/src/screenshots/orchestrate.ts +1 -1
- package/src/screenshots/org-deck.ts +1 -1
- package/src/screenshots/schema.ts +2 -2
- package/src/sim-client.ts +1 -1
- package/src/skills/builtin/compat-check.ts +2 -2
- package/src/swift-compile.ts +359 -0
- package/src/swift-package.ts +69 -0
- package/src/swift-submit.ts +3 -2
- package/src/vite-plugin-one.ts +8 -8
- package/src/vite-plugin-swift.ts +106 -110
- 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
|
+
}
|
package/src/swift-submit.ts
CHANGED
|
@@ -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
|
-
*
|
|
11
|
-
*
|
|
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
|
package/src/vite-plugin-one.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// sootsim plugin for One/Vite — serves the installed sootsim runtime at /__soot/
|
|
2
|
-
// from ~/.
|
|
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
|
|
39
|
-
// server is listening. skipped when CI or
|
|
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: `
|
|
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 (
|
|
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
|
|
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(`[
|
|
233
|
+
console.log(`[peach] serving at ${sootsimUrl}`)
|
|
234
234
|
|
|
235
|
-
if (appName && !process.env.CI && !process.env.
|
|
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(
|