peachsim 0.1.570 → 0.1.571
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/README.md +2 -2
- package/cli/bridge-flow-runner.ts +15 -7
- package/cli/cloud-dispatch.ts +1 -0
- package/cli/cloud-test-result-files.ts +97 -0
- package/cli/commands/maestro.ts +123 -31
- package/cli/commands/test.ts +161 -3
- package/cli/maestro-report.ts +3 -1
- package/cli/outbound-endpoints.ts +2 -0
- package/cli/run-registry.ts +2 -1
- package/detox/jest-preset.cjs +2 -2
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- 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 +1 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- 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 +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/detox/jest-preset.cjs +2 -2
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +16 -7
- 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 +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +16 -26
- 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 +1 -1
- package/dist-lib/menu.mjs +1 -1
- 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 +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- 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 +1 -1
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +119 -106
- 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 +456 -138
- package/package.json +22 -1
- package/skills/rnx-test/SKILL.md +3 -3
- package/src/cloud-test-args.ts +100 -0
- package/src/rolldown-plugin-swift.ts +8 -7
- 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-swift.ts +103 -107
package/src/vite-plugin-swift.ts
CHANGED
|
@@ -1,42 +1,30 @@
|
|
|
1
|
-
// `.swift` handling for a vite dev server:
|
|
2
|
-
//
|
|
3
|
-
// and answer every `.swift` import with the
|
|
1
|
+
// `.swift` handling for a vite dev server: send the project source set through
|
|
2
|
+
// the same project-unit compiler as `/api/swift-compile`, serve the resulting
|
|
3
|
+
// artifact at a content-hashed url, and answer every `.swift` import with the
|
|
4
|
+
// module that mounts it.
|
|
4
5
|
//
|
|
5
6
|
// two hosts use this. the bundler that builds the app's native bundle runs
|
|
6
7
|
// resolveId/load, and the dev server runs the middleware that serves the
|
|
7
8
|
// artifact. both share the build below, so the artifact is compiled once.
|
|
8
9
|
//
|
|
9
|
-
// the hash
|
|
10
|
-
//
|
|
11
|
-
// remounting.
|
|
10
|
+
// the hash covers the same source/module/toolchain inputs as the remote
|
|
11
|
+
// service, so a new hash means a genuinely different app: createSwiftEntry
|
|
12
|
+
// moves the @State store across it instead of remounting.
|
|
12
13
|
|
|
13
|
-
import { execFile } from 'node:child_process'
|
|
14
14
|
import { createHash } from 'node:crypto'
|
|
15
15
|
import fs from 'node:fs'
|
|
16
16
|
import os from 'node:os'
|
|
17
17
|
import path from 'node:path'
|
|
18
|
-
import { promisify } from 'node:util'
|
|
19
18
|
import {
|
|
20
19
|
enumerateBundleImages,
|
|
21
20
|
enumerateBundleJson,
|
|
22
21
|
} from '../../sootsim-swift/src/bundleAssets'
|
|
23
22
|
import { swiftEntryModuleEsm } from '../../sootsim-swift/src/entryModule'
|
|
24
|
-
import
|
|
23
|
+
import { compileSwiftProjectUnit, swiftModuleInputFiles } from './swift-compile'
|
|
24
|
+
import type { SwiftCompileInput } from './swift-compile'
|
|
25
|
+
import type { SwiftImageUpload } from './swift-submit'
|
|
25
26
|
import type { Plugin, ViteDevServer } from 'vite'
|
|
26
27
|
|
|
27
|
-
const run = promisify(execFile)
|
|
28
|
-
|
|
29
|
-
// the swift.org release scripts/build.sh builds with, from the one place it is
|
|
30
|
-
// named: the toolchain is <release>.xctoolchain and its matching wasm sdk is
|
|
31
|
-
// <release>_wasm. xcode's swift has no wasm backend, so this toolchain is the
|
|
32
|
-
// only one that can build the target.
|
|
33
|
-
const TOOLCHAIN_BIN = path.join(
|
|
34
|
-
os.homedir(),
|
|
35
|
-
`Library/Developer/Toolchains/${toolchain.release}.xctoolchain/usr/bin`,
|
|
36
|
-
)
|
|
37
|
-
const WASM_SDK = `${toolchain.release}_wasm`
|
|
38
|
-
const WASM_TRIPLE = 'wasm32-unknown-wasip1'
|
|
39
|
-
|
|
40
28
|
const ARTIFACT_PATH = '/__rnx/swift/'
|
|
41
29
|
const RESOURCE_PATH = '/__rnx/swift-res/'
|
|
42
30
|
const VIRTUAL_PREFIX = '\0rnx-swift:'
|
|
@@ -141,6 +129,8 @@ const RESOURCE_CONTENT_TYPES: Record<string, string> = {
|
|
|
141
129
|
export interface SwiftResourceTable {
|
|
142
130
|
/** bundle asset name to absolute serving url. */
|
|
143
131
|
table: Record<string, string>
|
|
132
|
+
/** the same rasters as compile-address inputs. */
|
|
133
|
+
images: Record<string, SwiftImageUpload>
|
|
144
134
|
/** project-relative image files this load's table covers (for watching). */
|
|
145
135
|
watched: string[]
|
|
146
136
|
}
|
|
@@ -168,6 +158,7 @@ export function buildSwiftResourceTable(
|
|
|
168
158
|
}
|
|
169
159
|
})
|
|
170
160
|
const table: Record<string, string> = {}
|
|
161
|
+
const images: Record<string, SwiftImageUpload> = {}
|
|
171
162
|
const watched: string[] = []
|
|
172
163
|
const registered = new Map<string, string>()
|
|
173
164
|
for (const { name, file } of found) {
|
|
@@ -182,6 +173,10 @@ export function buildSwiftResourceTable(
|
|
|
182
173
|
const hash = createHash('sha256').update(bytes).digest('hex').slice(0, 16)
|
|
183
174
|
const ext = path.posix.extname(file).toLowerCase()
|
|
184
175
|
table[name] = `${baseUrl}${RESOURCE_PATH}${hash}${ext}`
|
|
176
|
+
images[name] = {
|
|
177
|
+
file: path.posix.basename(file),
|
|
178
|
+
data: bytes.toString('base64'),
|
|
179
|
+
}
|
|
185
180
|
watched.push(file)
|
|
186
181
|
// a Contents.json edit (scale swap, file rename) re-resolves the same
|
|
187
182
|
// way a raster edit does, so the catalog rides the watcher too.
|
|
@@ -190,7 +185,7 @@ export function buildSwiftResourceTable(
|
|
|
190
185
|
registered.set(`${hash}${ext}`, disk)
|
|
191
186
|
}
|
|
192
187
|
project.resourceFiles = registered
|
|
193
|
-
return { table, watched }
|
|
188
|
+
return { table, images, watched }
|
|
194
189
|
}
|
|
195
190
|
|
|
196
191
|
// the /res pack: every Resources/ json in the package, basename-keyed and
|
|
@@ -293,22 +288,41 @@ export function linkedModuleRoots(packagePath: string): string[] {
|
|
|
293
288
|
return [...roots].sort()
|
|
294
289
|
}
|
|
295
290
|
|
|
296
|
-
// the stamp covers
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
// stable when a checkout restores byte-identical files. a restore rebuilds
|
|
304
|
-
// here and reuses the stored artifact there, by design.
|
|
291
|
+
// the stamp covers every file that contributes to compileInput plus the
|
|
292
|
+
// linked module and toolchain release that the shared artifact hash names. a
|
|
293
|
+
// running dev server rebuilds only when this moves, so its input closure must
|
|
294
|
+
// stay identical to the artifact address's even though this cheap watcher
|
|
295
|
+
// signal hashes mtimes while that stable address hashes content. a checkout
|
|
296
|
+
// restoring byte-identical files rebuilds here and reuses the same address
|
|
297
|
+
// there, by design.
|
|
305
298
|
export function sourceStamp(packagePath: string): string {
|
|
306
299
|
const hash = createHash('sha256')
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
300
|
+
hash.update(packagePath)
|
|
301
|
+
const projectInputs = new Set([
|
|
302
|
+
path.join(packagePath, 'Package.swift'),
|
|
303
|
+
...sourceFiles(packagePath),
|
|
304
|
+
...buildSwiftBundleFiles(packagePath).watched.map((file) =>
|
|
305
|
+
path.join(packagePath, ...file.split(path.posix.sep)),
|
|
306
|
+
),
|
|
307
|
+
...buildSwiftResourceTable(projectFor(packagePath), '').watched.map((file) =>
|
|
308
|
+
path.join(packagePath, ...file.split(path.posix.sep)),
|
|
309
|
+
),
|
|
310
|
+
])
|
|
311
|
+
for (const file of [...projectInputs].sort()) {
|
|
312
|
+
hash.update(path.relative(packagePath, file))
|
|
313
|
+
hash.update(String(fs.statSync(file).mtimeMs))
|
|
314
|
+
}
|
|
315
|
+
const moduleRoot = linkedModuleRoots(packagePath).find((root) =>
|
|
316
|
+
fs.existsSync(path.join(root, 'toolchain.json')),
|
|
317
|
+
)
|
|
318
|
+
if (moduleRoot) {
|
|
319
|
+
hash.update(moduleRoot)
|
|
320
|
+
for (const relative of [
|
|
321
|
+
...swiftModuleInputFiles(moduleRoot),
|
|
322
|
+
'toolchain.json',
|
|
323
|
+
].sort()) {
|
|
324
|
+
hash.update(relative)
|
|
325
|
+
hash.update(String(fs.statSync(path.join(moduleRoot, relative)).mtimeMs))
|
|
312
326
|
}
|
|
313
327
|
}
|
|
314
328
|
return hash.digest('hex').slice(0, 16)
|
|
@@ -339,82 +353,62 @@ function artifactUrl(project: SwiftProject, artifact: SwiftArtifact): string {
|
|
|
339
353
|
return `${serverOrigin()}${ARTIFACT_PATH}${artifact.hash}.wasm`
|
|
340
354
|
}
|
|
341
355
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
+
function compileInput(packagePath: string, entry: string): SwiftCompileInput {
|
|
357
|
+
const files: Record<string, string> = {}
|
|
358
|
+
for (const file of sourceFiles(packagePath)) {
|
|
359
|
+
const relative = path.relative(packagePath, file).split(path.sep).join(path.posix.sep)
|
|
360
|
+
files[relative] = fs.readFileSync(file, 'utf8')
|
|
361
|
+
}
|
|
362
|
+
const manifest = path.join(packagePath, 'Package.swift')
|
|
363
|
+
if (fs.existsSync(manifest)) files['Package.swift'] = fs.readFileSync(manifest, 'utf8')
|
|
364
|
+
const resources = buildSwiftBundleFiles(packagePath).files
|
|
365
|
+
const images = buildSwiftResourceTable(projectFor(packagePath), '').images
|
|
366
|
+
return {
|
|
367
|
+
files,
|
|
368
|
+
resources,
|
|
369
|
+
images,
|
|
370
|
+
entry,
|
|
356
371
|
}
|
|
357
|
-
return path.join(binDir, artifacts[0])
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function streamText(value: unknown): string {
|
|
361
|
-
if (typeof value === 'string') return value.trim()
|
|
362
|
-
if (Buffer.isBuffer(value)) return value.toString('utf8').trim()
|
|
363
|
-
return ''
|
|
364
372
|
}
|
|
365
373
|
|
|
366
|
-
async function compile(
|
|
367
|
-
|
|
368
|
-
|
|
374
|
+
async function compile(
|
|
375
|
+
packagePath: string,
|
|
376
|
+
entry: string,
|
|
377
|
+
stamp: string,
|
|
378
|
+
): Promise<SwiftArtifact> {
|
|
379
|
+
const moduleRoot = linkedModuleRoots(packagePath).find((root) =>
|
|
380
|
+
fs.existsSync(path.join(root, 'toolchain.json')),
|
|
381
|
+
)
|
|
382
|
+
if (!moduleRoot) {
|
|
369
383
|
throw new Error(
|
|
370
|
-
`
|
|
384
|
+
`swift project ${packagePath} has no linked rnx SwiftUI package with toolchain.json`,
|
|
371
385
|
)
|
|
372
386
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
'-c',
|
|
383
|
-
'release',
|
|
384
|
-
'-Xswiftc',
|
|
385
|
-
'-Osize',
|
|
386
|
-
],
|
|
387
|
-
{ maxBuffer: 64 * 1024 * 1024 },
|
|
388
|
-
)
|
|
389
|
-
} catch (error) {
|
|
390
|
-
// `swift build` prints the compiler's diagnostics on stdout and swiftpm's
|
|
391
|
-
// own on stderr; the failure's message holds only the command line, so
|
|
392
|
-
// both streams are what names the line that broke. stdout leads because
|
|
393
|
-
// compiler diagnostics are what the editor needs to show.
|
|
394
|
-
const streams =
|
|
395
|
-
error && typeof error === 'object'
|
|
396
|
-
? [
|
|
397
|
-
streamText(Reflect.get(error, 'stdout')),
|
|
398
|
-
streamText(Reflect.get(error, 'stderr')),
|
|
399
|
-
].filter(Boolean)
|
|
400
|
-
: []
|
|
401
|
-
const detail =
|
|
402
|
-
streams.length > 0
|
|
403
|
-
? streams.join('\n')
|
|
404
|
-
: error instanceof Error
|
|
405
|
-
? error.message
|
|
406
|
-
: String(error)
|
|
407
|
-
throw new Error(`swift build failed for ${packagePath}\n${detail}`)
|
|
408
|
-
}
|
|
409
|
-
const bytes = fs.readFileSync(builtArtifact(packagePath))
|
|
410
|
-
return {
|
|
411
|
-
hash: createHash('sha256').update(bytes).digest('hex').slice(0, 16),
|
|
412
|
-
bytes,
|
|
413
|
-
stamp,
|
|
387
|
+
const toolchain: unknown = JSON.parse(
|
|
388
|
+
fs.readFileSync(path.join(moduleRoot, 'toolchain.json'), 'utf8'),
|
|
389
|
+
)
|
|
390
|
+
const release =
|
|
391
|
+
toolchain && typeof toolchain === 'object'
|
|
392
|
+
? Reflect.get(toolchain, 'release')
|
|
393
|
+
: undefined
|
|
394
|
+
if (typeof release !== 'string') {
|
|
395
|
+
throw new Error(`swift toolchain manifest at ${moduleRoot} has no release`)
|
|
414
396
|
}
|
|
397
|
+
const artifact = await compileSwiftProjectUnit(compileInput(packagePath, entry), {
|
|
398
|
+
buildRoot:
|
|
399
|
+
process.env.CONTRAST_SWIFT_BUILD_ROOT ??
|
|
400
|
+
path.join(os.tmpdir(), 'contrast-swift-build'),
|
|
401
|
+
moduleRoot,
|
|
402
|
+
release,
|
|
403
|
+
toolchainBin: path.join(
|
|
404
|
+
os.homedir(),
|
|
405
|
+
`Library/Developer/Toolchains/${release}.xctoolchain/usr/bin`,
|
|
406
|
+
),
|
|
407
|
+
})
|
|
408
|
+
return { ...artifact, stamp }
|
|
415
409
|
}
|
|
416
410
|
|
|
417
|
-
function artifactFor(project: SwiftProject): Promise<SwiftArtifact> {
|
|
411
|
+
function artifactFor(project: SwiftProject, entry: string): Promise<SwiftArtifact> {
|
|
418
412
|
const stamp = sourceStamp(project.packagePath)
|
|
419
413
|
if (project.artifact?.stamp === stamp) return Promise.resolve(project.artifact)
|
|
420
414
|
if (project.building) {
|
|
@@ -424,10 +418,10 @@ function artifactFor(project: SwiftProject): Promise<SwiftArtifact> {
|
|
|
424
418
|
// would emit the older artifact and nothing would trigger another load.
|
|
425
419
|
const running = project.building
|
|
426
420
|
if (running.stamp === stamp) return running.promise
|
|
427
|
-
const again = () => artifactFor(project)
|
|
421
|
+
const again = () => artifactFor(project, entry)
|
|
428
422
|
return running.promise.then(again, again)
|
|
429
423
|
}
|
|
430
|
-
const promise = compile(project.packagePath, stamp).then(
|
|
424
|
+
const promise = compile(project.packagePath, entry, stamp).then(
|
|
431
425
|
(artifact) => {
|
|
432
426
|
project.artifact = artifact
|
|
433
427
|
project.building = null
|
|
@@ -499,11 +493,13 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
499
493
|
if (!fs.existsSync(file)) throw new Error(`swift source ${file} no longer exists`)
|
|
500
494
|
const packagePath = options.packagePath ?? packageFor(file)
|
|
501
495
|
const project = projectFor(packagePath)
|
|
496
|
+
const entry = path.relative(packagePath, file).split(path.sep).join(path.posix.sep)
|
|
502
497
|
// every source in the package is a dependency of this module, so a
|
|
503
498
|
// bundler watching this module learns about a `.swift` edit and re-runs
|
|
504
499
|
// load. that is the whole hot reload: the new hash in the emitted module
|
|
505
500
|
// is what tells the mount it is looking at a different app.
|
|
506
501
|
for (const source of sourceFiles(packagePath)) this.addWatchFile(source)
|
|
502
|
+
this.addWatchFile(path.join(packagePath, 'Package.swift'))
|
|
507
503
|
// an edit to the linked SwiftUI module rebuilds the artifact without
|
|
508
504
|
// touching the project's own sources, so those files gate the reload
|
|
509
505
|
// too: the new hash in the emitted module is what tells the mount it
|
|
@@ -513,7 +509,7 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
513
509
|
}
|
|
514
510
|
let artifact: SwiftArtifact
|
|
515
511
|
try {
|
|
516
|
-
artifact = await artifactFor(project)
|
|
512
|
+
artifact = await artifactFor(project, entry)
|
|
517
513
|
} catch (error) {
|
|
518
514
|
// a compile error must not fail this module. a module whose load threw
|
|
519
515
|
// leaves the graph unwatched, so the edit that fixes the error would
|