peachsim 0.1.1620 → 0.1.1635
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 +45 -6
- package/cli/app-config.ts +4 -1
- package/cli/app-fonts.ts +39 -18
- package/cli/app-stubs.ts +224 -9
- package/cli/bridge-flow-runner.ts +248 -156
- package/cli/cloud-client.ts +68 -57
- package/cli/command-registry.ts +1 -0
- package/cli/commands/box/search-command.ts +3 -2
- package/cli/commands/box.ts +10 -29
- package/cli/commands/control.ts +391 -92
- package/cli/commands/doctor.ts +17 -184
- package/cli/commands/flow.ts +79 -11
- package/cli/commands/inspect/core.ts +10 -5
- package/cli/commands/inspect/settling.ts +71 -22
- package/cli/commands/inspect/wait-ready.ts +12 -0
- package/cli/commands/inspect.ts +26 -3
- package/cli/commands/install-desktop.ts +1 -0
- package/cli/commands/maestro-compare.ts +105 -67
- package/cli/commands/maestro.ts +9 -1
- package/cli/commands/platform.ts +71 -36
- package/cli/commands/record.ts +21 -1
- package/cli/commands/screenshot.ts +23 -1
- package/cli/commands/serve.ts +7 -2
- package/cli/commands/setup.ts +19 -42
- package/cli/commands/swift.ts +5 -0
- package/cli/commands/upload.ts +51 -8
- package/cli/desktop-companion.ts +102 -17
- package/cli/dev-build-freshness.ts +37 -2
- package/cli/drivers/desktop.ts +14 -1
- package/cli/drivers/playwright-sim-host.ts +44 -8
- package/cli/drivers/playwright.ts +9 -8
- package/cli/drivers/types.ts +5 -0
- package/cli/maestro-accessibility.ts +53 -0
- package/cli/maestro-compare.ts +108 -12
- package/cli/main.ts +2 -1
- package/cli/outbound-endpoints.ts +35 -6
- package/cli/parent-pid.ts +0 -9
- package/cli/pause-aware-timeout.ts +102 -0
- package/cli/remote-simulators.ts +112 -0
- package/cli/remoteSimulatorContract.ts +21 -0
- package/cli/scene-video.ts +89 -0
- package/cli/ws-bridge.ts +23 -14
- package/detox/attempt-events.cjs +4 -0
- package/detox/expectations.ts +5 -1
- package/detox/index.ts +241 -181
- package/detox/motion-change.cjs +73 -0
- package/detox/motion-change.d.cts +21 -0
- package/detox/navigation-lifecycle.ts +22 -4
- package/dist-lib/agent-daemon-client.cjs +3 -2
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +3 -2
- 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 +13 -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 +14 -8
- package/dist-lib/bridge-contract.mjs +13 -7
- package/dist-lib/bundle-channel.cjs +10480 -7962
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +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 +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +3 -2
- package/dist-lib/cloud.mjs +3 -2
- package/dist-lib/config.cjs +10 -6
- package/dist-lib/config.mjs +10 -6
- package/dist-lib/detox/index.cjs +1854 -966
- package/dist-lib/dev-bundle-resolution.cjs +64 -24
- package/dist-lib/expo-swift-bundle.cjs +8715 -6271
- package/dist-lib/expo-swift-metadata.cjs +7 -4
- package/dist-lib/expo-swift-metadata.mjs +7 -4
- package/dist-lib/home-paths.cjs +32 -3
- package/dist-lib/host/bridge-host.cjs +10135 -7060
- 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 +1843 -408
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/kotlin-host.cjs +1 -1
- package/dist-lib/menu.cjs +3 -2
- package/dist-lib/menu.mjs +3 -2
- 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 +5 -4
- package/dist-lib/metro-production-bundle.mjs +5 -4
- package/dist-lib/metro.cjs +1182 -418
- package/dist-lib/native-app-identity-project.cjs +782 -23
- package/dist-lib/native-app-identity-project.mjs +781 -23
- package/dist-lib/native-app-identity.cjs +276 -9
- package/dist-lib/native-app-identity.mjs +273 -8
- package/dist-lib/native-firebase-metadata.cjs +5 -4
- package/dist-lib/native-firebase-metadata.mjs +5 -4
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +3 -2
- package/dist-lib/react-native-host-modules.cjs +8 -1
- package/dist-lib/react-native-host-modules.mjs +8 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +106 -28
- package/dist-lib/sdk.cjs +115 -34
- package/dist-lib/sdk.mjs +115 -33
- package/dist-lib/skills.cjs +15274 -9904
- package/dist-lib/swift-compile.cjs +1165 -173
- package/dist-lib/swift-compile.mjs +1212 -203
- package/dist-lib/swift-package.cjs +12 -3
- package/dist-lib/swift-package.mjs +12 -3
- package/dist-lib/swift-submit.cjs +7 -1
- package/dist-lib/swift-submit.mjs +5 -1
- package/dist-lib/swift-toolchain.cjs +133 -18
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +2648 -862
- package/dist-lib/vite.cjs +70 -14
- package/dist-lib/worklet-executable.cjs +25 -2
- package/dist-lib/worklet-executable.mjs +25 -2
- package/kotlin/AppSources/dev/peach/app/PeachDrawable.kt +21 -14
- package/kotlin/Sources/androidx/annotation/DrawableRes.kt +14 -0
- package/package.json +2 -2
- package/scripts/dev-server-scanner.ts +35 -14
- package/skills/peach-perf/SKILL.md +11 -5
- package/skills/peach-setup/SKILL.md +16 -19
- package/src/admitted-mac-build.ts +34 -8
- package/src/android-app-identity.ts +146 -0
- package/src/android-autolinking.ts +75 -0
- package/src/android-string-value.ts +36 -0
- package/src/android-strings.ts +5 -36
- package/src/androidAutolinkingContract.ts +40 -0
- package/src/bridge-constants.ts +11 -0
- package/src/bridge-contract.ts +22 -34
- package/src/bundleGlobalFooter.ts +26 -7
- package/src/codex-client.ts +3 -2
- package/src/config.ts +15 -0
- package/src/desktop-host-delivery.ts +5 -1
- package/src/dev-bundle-resolution.ts +60 -26
- package/src/expo-kotlin-host.ts +6 -36
- package/src/expo-manifest-body.ts +13 -0
- package/src/expo-swift-bundle.ts +17 -12
- package/src/expo-swift-host.ts +640 -192
- package/src/expo-swift-ready.ts +78 -0
- package/src/expoSwiftContract.ts +19 -0
- package/src/home-paths.ts +48 -2
- package/src/host/bridge-host.ts +12 -57
- package/src/host/staged-host-files.ts +29 -2
- package/src/installed-package-versions.ts +54 -14
- package/src/launchMetadataContract.ts +24 -0
- package/src/metro-plugin.ts +37 -42
- package/src/native-app-identity-project.ts +142 -5
- package/src/native-app-identity.ts +176 -11
- package/src/native-firebase-config.ts +209 -13
- package/src/nitro-compile.ts +2 -5
- package/src/nitro-source.ts +147 -63
- package/src/process-group.ts +114 -61
- package/src/process-output.ts +0 -9
- package/src/public-brand.ts +1 -0
- package/src/react-native-bridge-modules.ts +416 -0
- package/src/react-native-host-modules.ts +7 -0
- package/src/sim-client.ts +6 -4
- package/src/swift-binary-modules.ts +2 -2
- package/src/swift-compile.ts +1007 -108
- package/src/swift-compiler-digest.ts +14 -0
- package/src/swift-distribution.lock.json +40 -12
- package/src/swift-distribution.ts +61 -9
- package/src/swift-host.ts +149 -56
- package/src/swift-icu.ts +14 -6
- package/src/swift-objc-sdk.ts +133 -10
- package/src/swift-objc-standin.ts +38 -3
- package/src/swift-package-manifest.ts +21 -6
- package/src/swift-package.ts +15 -1
- package/src/swift-packages.ts +119 -60
- package/src/swift-standin-emit.ts +57 -9
- package/src/swift-standins.ts +185 -25
- package/src/swift-submit.ts +10 -0
- package/src/swift-toolchain.ts +149 -23
- package/src/vite-plugin.ts +20 -20
- package/src/worklet-executable.ts +30 -3
- package/src/worklets-babel.ts +35 -14
- package/src/xcode-project.ts +10 -0
package/cli/commands/doctor.ts
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
// native code Peach does not know with how to stub it, and a no-login report.
|
|
5
5
|
|
|
6
6
|
import { createHash } from 'node:crypto'
|
|
7
|
-
import {
|
|
8
|
-
import { dirname, join, relative, resolve } from 'node:path'
|
|
7
|
+
import { relative, resolve } from 'node:path'
|
|
9
8
|
import { PURE_JS_PACKAGES } from '@peach/compat/pure-js-whitelist'
|
|
10
9
|
import { resolveCompatVersionRange } from '@peach/compat/version-range'
|
|
11
10
|
import {
|
|
@@ -20,11 +19,11 @@ import { rnxPublicBrand } from '../../src/public-brand'
|
|
|
20
19
|
import { resolveRNXAppConfig } from '../app-config'
|
|
21
20
|
import {
|
|
22
21
|
installedPackageDir,
|
|
23
|
-
|
|
22
|
+
hasNativeCode,
|
|
23
|
+
scanLocalNative,
|
|
24
|
+
type LocalNative,
|
|
25
|
+
type AnsweredNames,
|
|
24
26
|
packageNativeRequests,
|
|
25
|
-
sourceNativeRequests,
|
|
26
|
-
readSmallFile,
|
|
27
|
-
SOURCE_FILE,
|
|
28
27
|
type AppStubs,
|
|
29
28
|
type AppliedStubs,
|
|
30
29
|
type NativeRequest as PackageNativeRequest,
|
|
@@ -33,11 +32,8 @@ import { readRuntimeCompatibility } from '../runtime-compat'
|
|
|
33
32
|
import { accent, bold, failure, muted, success, warning } from '../ui'
|
|
34
33
|
import {
|
|
35
34
|
findProjectRoot,
|
|
36
|
-
scanRepository,
|
|
37
35
|
scanWorkspace,
|
|
38
36
|
installedManifest,
|
|
39
|
-
metadataValue,
|
|
40
|
-
readProjectJson,
|
|
41
37
|
readProjectManifest,
|
|
42
38
|
} from './detect'
|
|
43
39
|
import { sanitizeProjectReportName, sanitizeProjectReportVersion } from './project-report'
|
|
@@ -129,7 +125,18 @@ export async function runDoctor(args: string[]): Promise<number> {
|
|
|
129
125
|
versions: checkVersions(target.installed, runtime.appSupport),
|
|
130
126
|
scan: target.scan,
|
|
131
127
|
unknownNative: findUnknownNative(target.dir, target.scan, answered),
|
|
132
|
-
local: scanLocalNative(
|
|
128
|
+
local: scanLocalNative(
|
|
129
|
+
target.dir,
|
|
130
|
+
target.manifest,
|
|
131
|
+
answered,
|
|
132
|
+
target.scan.scoreSummary.platform,
|
|
133
|
+
new Set([
|
|
134
|
+
...PURE_JS_PACKAGES,
|
|
135
|
+
...target.scan.packages
|
|
136
|
+
.filter((item) => item.rnx.source !== 'untracked')
|
|
137
|
+
.map((item) => item.name),
|
|
138
|
+
]),
|
|
139
|
+
),
|
|
133
140
|
}
|
|
134
141
|
|
|
135
142
|
if (json) printJson(findings)
|
|
@@ -817,23 +824,6 @@ function findUnknownNative(
|
|
|
817
824
|
})
|
|
818
825
|
}
|
|
819
826
|
|
|
820
|
-
function hasNativeCode(
|
|
821
|
-
dir: string,
|
|
822
|
-
platform: ScanResult['scoreSummary']['platform'],
|
|
823
|
-
): boolean {
|
|
824
|
-
const expoConfig = join(dir, 'expo-module.config.json')
|
|
825
|
-
const platforms = metadataValue(readProjectJson(expoConfig), 'platforms')
|
|
826
|
-
if (Array.isArray(platforms))
|
|
827
|
-
return (
|
|
828
|
-
platforms.includes(platform) || (platform === 'ios' && platforms.includes('apple'))
|
|
829
|
-
)
|
|
830
|
-
if ([platform, 'expo-module.config.json'].some((entry) => existsSync(join(dir, entry))))
|
|
831
|
-
return true
|
|
832
|
-
if (listDir(dir).some((name) => name.endsWith('.podspec'))) return true
|
|
833
|
-
const manifest = readSmallFile(join(dir, 'package.json'))
|
|
834
|
-
return manifest !== null && manifest.includes('"codegenConfig"')
|
|
835
|
-
}
|
|
836
|
-
|
|
837
827
|
function reportLocalNative(local: readonly LocalNative[]): void {
|
|
838
828
|
for (const item of local) {
|
|
839
829
|
const where = item.expoModule
|
|
@@ -845,26 +835,6 @@ function reportLocalNative(local: readonly LocalNative[]): void {
|
|
|
845
835
|
}
|
|
846
836
|
}
|
|
847
837
|
|
|
848
|
-
// an app's own native code, found from the names its JavaScript asks the native
|
|
849
|
-
// side for and from local Expo modules (a directory with expo-module.config.json).
|
|
850
|
-
// each name is a key under its package in peach.config.ts `packages`.
|
|
851
|
-
export interface LocalNative {
|
|
852
|
-
kind: 'module' | 'view'
|
|
853
|
-
name: string
|
|
854
|
-
// the package the name belongs to: the nearest package.json with a name
|
|
855
|
-
package: string
|
|
856
|
-
// first place the name appears, relative to the app, as file:line
|
|
857
|
-
path: string
|
|
858
|
-
// the local Expo module directory the name belongs to, relative to the app
|
|
859
|
-
expoModule: string | null
|
|
860
|
-
swift: boolean
|
|
861
|
-
// a stub already answers it
|
|
862
|
-
answered: boolean
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
const NATIVE_SOURCE_FILE = /\.(?:swift|kt)$/
|
|
866
|
-
type AnsweredNames = Record<LocalNative['kind'], Set<string>>
|
|
867
|
-
|
|
868
838
|
function answeredNames(
|
|
869
839
|
config: Awaited<ReturnType<typeof resolveRNXAppConfig>>['config'],
|
|
870
840
|
): AnsweredNames {
|
|
@@ -878,143 +848,6 @@ function answeredNames(
|
|
|
878
848
|
}
|
|
879
849
|
}
|
|
880
850
|
|
|
881
|
-
function scanLocalNative(
|
|
882
|
-
appDir: string,
|
|
883
|
-
pkg: ProjectManifest,
|
|
884
|
-
answered: AnsweredNames,
|
|
885
|
-
scan: ScanResult,
|
|
886
|
-
): LocalNative[] {
|
|
887
|
-
// linking a known dependency into a workspace does not make it app-owned native code.
|
|
888
|
-
const knownPackages = new Set([
|
|
889
|
-
...PURE_JS_PACKAGES,
|
|
890
|
-
...scan.packages
|
|
891
|
-
.filter((item) => item.rnx.source !== 'untracked')
|
|
892
|
-
.map((item) => item.name),
|
|
893
|
-
])
|
|
894
|
-
const expoModuleDirs: string[] = []
|
|
895
|
-
const excludedModuleDirs: string[] = []
|
|
896
|
-
const sourceFiles: string[] = []
|
|
897
|
-
for (const root of [appDir, ...workspacePackageDirs(appDir, pkg)]) {
|
|
898
|
-
for (const dir of scanRepository(root).directories) {
|
|
899
|
-
if (
|
|
900
|
-
excludedModuleDirs.some(
|
|
901
|
-
(excluded) => dir === excluded || dir.startsWith(`${excluded}/`),
|
|
902
|
-
)
|
|
903
|
-
)
|
|
904
|
-
continue
|
|
905
|
-
if (existsSync(join(dir, 'expo-module.config.json'))) {
|
|
906
|
-
if (!hasNativeCode(dir, scan.scoreSummary.platform)) {
|
|
907
|
-
excludedModuleDirs.push(dir)
|
|
908
|
-
continue
|
|
909
|
-
}
|
|
910
|
-
expoModuleDirs.push(dir)
|
|
911
|
-
}
|
|
912
|
-
for (const name of listDir(dir)) {
|
|
913
|
-
if (SOURCE_FILE.test(name) && !name.endsWith('.d.ts'))
|
|
914
|
-
sourceFiles.push(join(dir, name))
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
const nativeFilesByModule = new Map(
|
|
919
|
-
expoModuleDirs.map((dir) => [
|
|
920
|
-
dir,
|
|
921
|
-
scanRepository(dir).directories.flatMap((sub) =>
|
|
922
|
-
listDir(sub)
|
|
923
|
-
.filter((name) => NATIVE_SOURCE_FILE.test(name))
|
|
924
|
-
.map((name) => join(sub, name)),
|
|
925
|
-
),
|
|
926
|
-
]),
|
|
927
|
-
)
|
|
928
|
-
|
|
929
|
-
const owners = new Map<string, string>()
|
|
930
|
-
const owner = (dir: string): string => {
|
|
931
|
-
const known = owners.get(dir)
|
|
932
|
-
if (known) return known
|
|
933
|
-
const name = readSmallFile(join(dir, 'package.json'))?.match(
|
|
934
|
-
/"name"\s*:\s*"([^"]+)"/,
|
|
935
|
-
)?.[1]
|
|
936
|
-
const parent = dirname(dir)
|
|
937
|
-
const result = name ?? (parent === dir ? (pkg.name ?? 'app') : owner(parent))
|
|
938
|
-
owners.set(dir, result)
|
|
939
|
-
return result
|
|
940
|
-
}
|
|
941
|
-
const found = new Map<string, LocalNative>()
|
|
942
|
-
const add = (kind: LocalNative['kind'], name: string, file: string, line: number) => {
|
|
943
|
-
const packageName = owner(dirname(file))
|
|
944
|
-
if (knownPackages.has(packageName)) return
|
|
945
|
-
const key = `${kind}:${name}`
|
|
946
|
-
if (found.has(key)) return
|
|
947
|
-
const moduleDir = expoModuleDirs.find((dir) => file.startsWith(`${dir}/`)) ?? null
|
|
948
|
-
found.set(key, {
|
|
949
|
-
kind,
|
|
950
|
-
name,
|
|
951
|
-
package: packageName,
|
|
952
|
-
path: `${relative(appDir, file)}:${line}`,
|
|
953
|
-
expoModule: moduleDir ? relative(appDir, moduleDir) || '.' : null,
|
|
954
|
-
swift: moduleDir
|
|
955
|
-
? (nativeFilesByModule.get(moduleDir) ?? []).some((path) =>
|
|
956
|
-
path.endsWith('.swift'),
|
|
957
|
-
)
|
|
958
|
-
: false,
|
|
959
|
-
answered: answered[kind].has(name),
|
|
960
|
-
})
|
|
961
|
-
}
|
|
962
|
-
for (const file of sourceFiles) {
|
|
963
|
-
const text = readSmallFile(file)
|
|
964
|
-
if (!text) continue
|
|
965
|
-
for (const { kind, name, line } of sourceNativeRequests(file, text))
|
|
966
|
-
add(kind, name, file, line)
|
|
967
|
-
}
|
|
968
|
-
// a local expo module declares its name in its native definition, Name("X"),
|
|
969
|
-
// whether or not the app's javascript spells it out. swift first, so a
|
|
970
|
-
// module the report marks Swift points at its swift definition.
|
|
971
|
-
for (const [, files] of nativeFilesByModule) {
|
|
972
|
-
const swiftFirst = [...files].sort(
|
|
973
|
-
(a, b) => Number(b.endsWith('.swift')) - Number(a.endsWith('.swift')),
|
|
974
|
-
)
|
|
975
|
-
for (const file of swiftFirst) {
|
|
976
|
-
const text = readSmallFile(file)
|
|
977
|
-
if (!text) continue
|
|
978
|
-
for (const match of text.matchAll(/\bName\(\s*"(\w+)"\s*\)/g)) {
|
|
979
|
-
if (match[1])
|
|
980
|
-
add('module', match[1], file, text.slice(0, match.index).split('\n').length)
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
return [...found.values()].sort(
|
|
985
|
-
(a, b) => a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name),
|
|
986
|
-
)
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
// dependencies installed as links to source outside any node_modules are the
|
|
990
|
-
// app's own workspace packages, which hold its native code as often as the app
|
|
991
|
-
// directory does.
|
|
992
|
-
function workspacePackageDirs(appDir: string, pkg: ProjectManifest): string[] {
|
|
993
|
-
const dirs = new Set<string>()
|
|
994
|
-
for (const name of Object.keys({ ...pkg.dependencies, ...pkg.devDependencies })) {
|
|
995
|
-
let current = appDir
|
|
996
|
-
while (true) {
|
|
997
|
-
const link = join(current, 'node_modules', name)
|
|
998
|
-
if (existsSync(link)) {
|
|
999
|
-
if (lstatSync(link).isSymbolicLink()) {
|
|
1000
|
-
const target = realpathSync(link)
|
|
1001
|
-
if (
|
|
1002
|
-
!target.split('/').includes('node_modules') &&
|
|
1003
|
-
!target.startsWith(`${appDir}/`)
|
|
1004
|
-
) {
|
|
1005
|
-
dirs.add(target)
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
break
|
|
1009
|
-
}
|
|
1010
|
-
const parent = dirname(current)
|
|
1011
|
-
if (parent === current) break
|
|
1012
|
-
current = parent
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
return [...dirs]
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
851
|
function scanOnePackage(
|
|
1019
852
|
appDir: string,
|
|
1020
853
|
pkg: ProjectManifest,
|
package/cli/commands/flow.ts
CHANGED
|
@@ -434,20 +434,20 @@ flow extension:
|
|
|
434
434
|
const remapArgs = args.flatMap((arg, index) =>
|
|
435
435
|
args[index - 1] === '--remap' ? ['--remap', arg] : [],
|
|
436
436
|
)
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
|
|
440
|
-
|
|
437
|
+
// CI previews export video from the saved scene after the take. desktop
|
|
438
|
+
// previews can also record live video; explicit --record always requests it.
|
|
439
|
+
const record =
|
|
440
|
+
args.includes('--record') || (preview && process.env.GITHUB_ACTIONS !== 'true')
|
|
441
441
|
// hold the final UI state in the recording so viewers see the result
|
|
442
442
|
// of the flow, not just a frame mid-transition. short flows otherwise
|
|
443
443
|
// cut off ~600ms in. caller can override with --tail-wait <ms>.
|
|
444
444
|
const tailWaitMs = args.includes('--tail-wait')
|
|
445
445
|
? Math.max(0, +(getFlag('--tail-wait') || '2000'))
|
|
446
|
-
:
|
|
447
|
-
?
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
446
|
+
: preview
|
|
447
|
+
? 6000
|
|
448
|
+
: record
|
|
449
|
+
? 2000
|
|
450
|
+
: 0
|
|
451
451
|
|
|
452
452
|
// parse flow file (frontmatter + steps). `${...}` templates stay verbatim
|
|
453
453
|
// here — the runner interpolates per step at execution time.
|
|
@@ -730,6 +730,9 @@ flow extension:
|
|
|
730
730
|
let videoPath: string | null = null
|
|
731
731
|
let videoDurationMs: number | null = null
|
|
732
732
|
let capturedEvents: Array<Record<string, any>> | null = null
|
|
733
|
+
let sceneRecording = false
|
|
734
|
+
let scenePath: string | null = null
|
|
735
|
+
let previewRecordingStartedAtMs: number | null = null
|
|
733
736
|
let previewScreenshotPath: string | null = null
|
|
734
737
|
let failureBundleDir: string | null = null
|
|
735
738
|
let exitCode = 0
|
|
@@ -799,14 +802,17 @@ flow extension:
|
|
|
799
802
|
simId: driver.simId,
|
|
800
803
|
code: `(() => {
|
|
801
804
|
const r = window.SootSim?.bridges?.eventRecorder ?? window.__sootsimEventRecorder
|
|
802
|
-
|
|
805
|
+
const startedAtMs = Date.now()
|
|
806
|
+
return r?.start?.(startedAtMs) ? startedAtMs : null
|
|
803
807
|
})()`,
|
|
804
808
|
})
|
|
809
|
+
if (typeof started === 'number') previewRecordingStartedAtMs = started
|
|
805
810
|
if (!started) {
|
|
806
811
|
console.warn(
|
|
807
812
|
' warn: eventRecorder.start() returned false — preview will upload bundle without replay events',
|
|
808
813
|
)
|
|
809
814
|
}
|
|
815
|
+
sceneRecording = await startPreviewSceneRecording(bridge, driver.simId)
|
|
810
816
|
}
|
|
811
817
|
await hooks.beforeSteps?.()
|
|
812
818
|
if (hooks.runSteps) await hooks.runSteps(driver, steps)
|
|
@@ -830,6 +836,10 @@ flow extension:
|
|
|
830
836
|
})()`,
|
|
831
837
|
})) as Array<Record<string, any>>
|
|
832
838
|
console.log(` captured ${capturedEvents.length} replay events`)
|
|
839
|
+
if (sceneRecording) {
|
|
840
|
+
sceneRecording = false
|
|
841
|
+
scenePath = await stopPreviewSceneRecording(bridge, driver.simId)
|
|
842
|
+
}
|
|
833
843
|
previewScreenshotPath = path.join(screenshotDir, 'preview-final.png')
|
|
834
844
|
await driver.captureScreenshot(previewScreenshotPath)
|
|
835
845
|
console.log(` final screenshot: ${previewScreenshotPath}`)
|
|
@@ -887,6 +897,10 @@ flow extension:
|
|
|
887
897
|
)
|
|
888
898
|
}
|
|
889
899
|
}
|
|
900
|
+
if (sceneRecording) {
|
|
901
|
+
sceneRecording = false
|
|
902
|
+
scenePath = await stopPreviewSceneRecording(bridge, driver.simId)
|
|
903
|
+
}
|
|
890
904
|
lastFlowError = err.message
|
|
891
905
|
console.error(`\n x failed: ${err.message}\n`)
|
|
892
906
|
if (profileResult) {
|
|
@@ -959,6 +973,7 @@ flow extension:
|
|
|
959
973
|
// canvas during scrub for smooth motion while the real app
|
|
960
974
|
// catches up.
|
|
961
975
|
if (videoPath) uploadArgs.push('--video', videoPath)
|
|
976
|
+
if (scenePath) uploadArgs.push('--scene', scenePath)
|
|
962
977
|
if (previewScreenshotPath) {
|
|
963
978
|
uploadArgs.push('--screenshot', previewScreenshotPath)
|
|
964
979
|
}
|
|
@@ -966,7 +981,8 @@ flow extension:
|
|
|
966
981
|
uploadArgs.push('--video-duration-ms', String(Math.round(videoDurationMs)))
|
|
967
982
|
uploadArgs.push('--recorded-duration-ms', String(Math.round(videoDurationMs)))
|
|
968
983
|
}
|
|
969
|
-
const recordingStartedAtMs =
|
|
984
|
+
const recordingStartedAtMs =
|
|
985
|
+
driver.getLastRecordingStartedAtMs() ?? previewRecordingStartedAtMs
|
|
970
986
|
if (recordingStartedAtMs && Number.isFinite(recordingStartedAtMs)) {
|
|
971
987
|
uploadArgs.push(
|
|
972
988
|
'--recording-started-at-ms',
|
|
@@ -1151,6 +1167,58 @@ async function prepareEventsFile(events: Array<Record<string, any>>): Promise<st
|
|
|
1151
1167
|
return outPath
|
|
1152
1168
|
}
|
|
1153
1169
|
|
|
1170
|
+
// the tab's scene stream rides the preview share. a tab with no app on screen
|
|
1171
|
+
// has none to record, and the share keeps its events and video.
|
|
1172
|
+
async function startPreviewSceneRecording(
|
|
1173
|
+
bridge: ReturnType<typeof createBridge>,
|
|
1174
|
+
simId: string | undefined,
|
|
1175
|
+
): Promise<boolean> {
|
|
1176
|
+
try {
|
|
1177
|
+
await bridge.send({
|
|
1178
|
+
type: 'evaluate',
|
|
1179
|
+
simId,
|
|
1180
|
+
code: `window.SootSim.bridges.sceneStream.run({ action: 'recordStart' })`,
|
|
1181
|
+
})
|
|
1182
|
+
return true
|
|
1183
|
+
} catch (err) {
|
|
1184
|
+
console.warn(
|
|
1185
|
+
` warn: scene stream not recorded: ${err instanceof Error ? err.message : String(err)}`,
|
|
1186
|
+
)
|
|
1187
|
+
return false
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// write the recorded scene stream to a temp file for the upload command.
|
|
1192
|
+
async function stopPreviewSceneRecording(
|
|
1193
|
+
bridge: ReturnType<typeof createBridge>,
|
|
1194
|
+
simId: string | undefined,
|
|
1195
|
+
): Promise<string | null> {
|
|
1196
|
+
try {
|
|
1197
|
+
const encoded = await bridge.send({
|
|
1198
|
+
type: 'evaluate',
|
|
1199
|
+
simId,
|
|
1200
|
+
code: `(async () => {
|
|
1201
|
+
const { bytes } = await window.SootSim.bridges.sceneStream.run({ action: 'recordStop' })
|
|
1202
|
+
let text = ''
|
|
1203
|
+
for (let i = 0; i < bytes.length; i += 0x8000)
|
|
1204
|
+
text += String.fromCharCode(...bytes.subarray(i, i + 0x8000))
|
|
1205
|
+
return btoa(text)
|
|
1206
|
+
})()`,
|
|
1207
|
+
})
|
|
1208
|
+
if (typeof encoded !== 'string') throw new Error('the tab returned no scene stream')
|
|
1209
|
+
const bytes = Buffer.from(encoded, 'base64')
|
|
1210
|
+
const outPath = path.join(tmpdir(), `peach-scene-${Date.now()}.psr.gz`)
|
|
1211
|
+
fs.writeFileSync(outPath, bytes)
|
|
1212
|
+
console.log(` scene stream: ${outPath} (${bytes.length} bytes gz)`)
|
|
1213
|
+
return outPath
|
|
1214
|
+
} catch (err) {
|
|
1215
|
+
console.warn(
|
|
1216
|
+
` warn: scene stream stop failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1217
|
+
)
|
|
1218
|
+
return null
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1154
1222
|
async function prepareFlowStepTimelineFile(
|
|
1155
1223
|
steps: SootSimFlowTraceStep[],
|
|
1156
1224
|
): Promise<string> {
|
|
@@ -13,17 +13,14 @@
|
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
HEADLESS_TENANT_FETCH_HUNG_MARKER,
|
|
16
|
+
readyProbeContentBasis,
|
|
16
17
|
readyProbeIsReady,
|
|
17
18
|
type SemanticWaitCondition,
|
|
18
19
|
type SemanticWaitResult,
|
|
19
20
|
type WsBridgeCommand,
|
|
20
21
|
} from '../../../src/bridge-contract'
|
|
21
22
|
import { SEMANTIC_WAIT_MAX_TIMEOUT_MS } from '../../../src/bridge-contract-input'
|
|
22
|
-
export {
|
|
23
|
-
readyProbeHasContent,
|
|
24
|
-
readyProbeHasTargetContent,
|
|
25
|
-
readyProbeIsReady,
|
|
26
|
-
} from '../../../src/bridge-contract'
|
|
23
|
+
export { readyProbeHasContent, readyProbeIsReady } from '../../../src/bridge-contract'
|
|
27
24
|
import type {
|
|
28
25
|
SimSemanticNode,
|
|
29
26
|
SimSemanticQueryResult,
|
|
@@ -1175,6 +1172,11 @@ export interface WaitReadyStatus {
|
|
|
1175
1172
|
externalError: string
|
|
1176
1173
|
suppressedEntryError: string
|
|
1177
1174
|
errors: number
|
|
1175
|
+
// the probe fields that decide readiness, so a ready result shows why it
|
|
1176
|
+
// returned: which content condition held and what painted.
|
|
1177
|
+
paintedAppContent: boolean
|
|
1178
|
+
treeQuietMs: number
|
|
1179
|
+
readyBasis: string
|
|
1178
1180
|
// non-empty when the probe itself stopped reaching the sim (tab closed,
|
|
1179
1181
|
// sim never connected) — the wait failed on the bridge, not on the app.
|
|
1180
1182
|
bridgeError: string
|
|
@@ -1422,6 +1424,9 @@ function waitReadyStatus(result: SemanticWaitResult, ready: boolean): WaitReadyS
|
|
|
1422
1424
|
suppressedEntryError: probe?.suppressedEntryError ?? '',
|
|
1423
1425
|
errors: probe?.errors ?? 0,
|
|
1424
1426
|
bridgeError: result.error ?? '',
|
|
1427
|
+
paintedAppContent: probe?.paintedAppContent ?? false,
|
|
1428
|
+
treeQuietMs: probe?.treeQuietMs ?? 0,
|
|
1429
|
+
readyBasis: probe ? readyProbeContentBasis(probe) : '',
|
|
1425
1430
|
}
|
|
1426
1431
|
}
|
|
1427
1432
|
|