peachsim 0.1.2228 → 0.1.2231
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/cli/commands/device.ts +1 -1
- package/cli/commands/flow.ts +1 -1
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.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/bundle-channel.cjs +35 -9
- 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 +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/config.mjs +1 -1
- package/dist-lib/detox/index.cjs +5 -2
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/expo-swift-bundle.cjs +33 -7
- package/dist-lib/expo-swift-metadata.cjs +1 -1
- package/dist-lib/expo-swift-metadata.mjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +76 -11
- package/dist-lib/host/fetch-proxy-handler.cjs +4 -4
- 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 +13 -5
- 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 +16 -14
- package/dist-lib/menu.mjs +16 -14
- 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 +7 -5
- package/dist-lib/native-app-identity-project.cjs +5 -3
- package/dist-lib/native-app-identity-project.mjs +5 -3
- package/dist-lib/native-app-identity.cjs +1 -1
- package/dist-lib/native-app-identity.mjs +1 -1
- package/dist-lib/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.mjs +1 -1
- package/dist-lib/pdfium-raster.cjs +1 -1
- package/dist-lib/pdfium-raster.mjs +1 -1
- 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 +1 -1
- package/dist-lib/public-origins-lib-esm.json +25 -24
- package/dist-lib/public-origins-lib.json +55 -53
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/reap-browser.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +43 -3
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +77 -12
- package/dist-lib/swift-compile.cjs +1 -1
- package/dist-lib/swift-compile.mjs +1 -1
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift-toolchain.cjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +32 -6
- package/dist-lib/vite.cjs +10 -4
- package/dist-lib/worklet-executable.cjs +1 -1
- package/dist-lib/worklet-executable.mjs +1 -1
- package/dist-types/packages/peach-engine/src/engine/canvaskit-api.d.ts +3 -0
- package/dist-types/packages/peach-engine/src/engine/native-layout.d.ts +10 -3
- package/dist-types/packages/peach-engine/src/engine/scene-table.d.ts +1 -1
- package/dist-types/packages/peach-engine/src/engine/types.d.ts +2 -1
- package/install.sh +12 -2
- package/kotlin/sdk.tar.gz +0 -0
- package/package.json +1 -1
- package/runtime-companion.json +1 -1
- package/scripts/dev-server-scanner.ts +64 -3
- package/src/appPackageContract.ts +1 -1
- package/src/conformance-pixel-comparison.ts +12 -7
- package/src/menu.ts +17 -99
- package/src/menuContract.ts +102 -0
- package/src/react-native-coverage/native-typescript-parser.ts +18 -8
- package/src/settingsContract.ts +8 -8
- package/src/swift-host.ts +27 -2
- package/src/vite-plugin-one.ts +2 -0
- package/src/vite-plugin.ts +9 -8
- package/src/xcode-project.ts +8 -2
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
parseSwiftHostDiscovery,
|
|
49
49
|
} from '../src/swiftDiscoveryContract'
|
|
50
50
|
import { loadOptionalDemoApps, optionalDemoAppsSync } from './optional-demo-registry.ts'
|
|
51
|
+
import type { ConfiguredOneWidget } from '../src/oneWidgetContract'
|
|
51
52
|
import type { DiscoveredServer } from '../src/serverDiscoveryContract'
|
|
52
53
|
|
|
53
54
|
export type { DiscoveredServer } from '../src/serverDiscoveryContract'
|
|
@@ -1187,6 +1188,38 @@ const knownNonPatched = new Set<number>()
|
|
|
1187
1188
|
// same invalidation rules as knownNonPatched: cleared on port
|
|
1188
1189
|
// disappearance, owning-pid change, and __resetScannerCache.
|
|
1189
1190
|
const knownOne = new Set<number>()
|
|
1191
|
+
const oneNativeApps = new Map<number, { root: string; bundleId: string }>()
|
|
1192
|
+
|
|
1193
|
+
function oneNativeWidgetFromScan(
|
|
1194
|
+
scan: unknown,
|
|
1195
|
+
result: DiscoveredServer,
|
|
1196
|
+
identity: { root: string; bundleId: string },
|
|
1197
|
+
): ConfiguredOneWidget | null {
|
|
1198
|
+
if (!Array.isArray(scan)) return null
|
|
1199
|
+
for (const entry of scan) {
|
|
1200
|
+
if (!entry || typeof entry !== 'object' || entry.port !== result.port) continue
|
|
1201
|
+
if (typeof entry.bundleUrl !== 'string') continue
|
|
1202
|
+
let advertised: URL
|
|
1203
|
+
try {
|
|
1204
|
+
advertised = new URL(entry.bundleUrl)
|
|
1205
|
+
} catch {
|
|
1206
|
+
continue
|
|
1207
|
+
}
|
|
1208
|
+
if (!isLoopbackHost(advertised.hostname) || advertised.port !== String(result.port))
|
|
1209
|
+
continue
|
|
1210
|
+
const widget = configuredWidgetFromScan(scan, advertised.href)
|
|
1211
|
+
if (
|
|
1212
|
+
widget?.projectRoot === identity.root &&
|
|
1213
|
+
widget.metadata.bundleId === identity.bundleId
|
|
1214
|
+
)
|
|
1215
|
+
return {
|
|
1216
|
+
...widget,
|
|
1217
|
+
bundleUrl: result.bundleUrl,
|
|
1218
|
+
packageUrl: new URL('/__peach/swift-package/OneWidgets', result.bundleUrl).href,
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
return null
|
|
1222
|
+
}
|
|
1190
1223
|
|
|
1191
1224
|
// fire signature probes in waves and pick the best match by precedence:
|
|
1192
1225
|
// 1. expo manifest (JSON manifest at /)
|
|
@@ -1260,6 +1293,25 @@ export async function probePort(
|
|
|
1260
1293
|
const manifestParsed = manifestRes ? safeParseManifest(manifestRes.body) : null
|
|
1261
1294
|
const { launchUrl: manifestLaunchUrl, client: manifestClient } =
|
|
1262
1295
|
readExpoManifestFields(manifestParsed)
|
|
1296
|
+
const metadata = manifestParsed?.metadata
|
|
1297
|
+
const extra = manifestParsed?.extra
|
|
1298
|
+
const expoGo = extra && typeof extra === 'object' ? Reflect.get(extra, 'expoGo') : null
|
|
1299
|
+
const developer =
|
|
1300
|
+
expoGo && typeof expoGo === 'object' ? Reflect.get(expoGo, 'developer') : null
|
|
1301
|
+
const projectRoot =
|
|
1302
|
+
developer && typeof developer === 'object'
|
|
1303
|
+
? Reflect.get(developer, 'projectRoot')
|
|
1304
|
+
: null
|
|
1305
|
+
const bundleId = manifestClient.ios?.bundleIdentifier
|
|
1306
|
+
if (
|
|
1307
|
+
metadata &&
|
|
1308
|
+
typeof metadata === 'object' &&
|
|
1309
|
+
Reflect.get(metadata, 'oneNativeApp') === true &&
|
|
1310
|
+
typeof projectRoot === 'string' &&
|
|
1311
|
+
typeof bundleId === 'string'
|
|
1312
|
+
)
|
|
1313
|
+
oneNativeApps.set(port, { root: projectRoot, bundleId })
|
|
1314
|
+
else if (manifestRes) oneNativeApps.delete(port)
|
|
1263
1315
|
// `manifestRes === null` means the probe got NO answer — it timed out or the
|
|
1264
1316
|
// connection failed. that is not the same fact as a server that answered and
|
|
1265
1317
|
// turned out to have no manifest, and only the second one licenses the
|
|
@@ -1636,6 +1688,7 @@ export function __resetScannerCache() {
|
|
|
1636
1688
|
portCache.clear()
|
|
1637
1689
|
knownNonPatched.clear()
|
|
1638
1690
|
knownOne.clear()
|
|
1691
|
+
oneNativeApps.clear()
|
|
1639
1692
|
enrichmentCache.clear()
|
|
1640
1693
|
enrichmentInFlight.clear()
|
|
1641
1694
|
portCacheInvalidatedAt.clear()
|
|
@@ -1692,6 +1745,9 @@ export async function scanDevServers(
|
|
|
1692
1745
|
for (const p of [...knownOne]) {
|
|
1693
1746
|
if (!currentPorts.has(p)) knownOne.delete(p)
|
|
1694
1747
|
}
|
|
1748
|
+
for (const p of [...oneNativeApps.keys()]) {
|
|
1749
|
+
if (!currentPorts.has(p)) oneNativeApps.delete(p)
|
|
1750
|
+
}
|
|
1695
1751
|
for (const p of [...enrichmentCache.keys()]) {
|
|
1696
1752
|
if (!currentPorts.has(p)) enrichmentCache.delete(p)
|
|
1697
1753
|
}
|
|
@@ -1726,6 +1782,7 @@ export async function scanDevServers(
|
|
|
1726
1782
|
if (cached && cached.pid !== pid) {
|
|
1727
1783
|
knownNonPatched.delete(port)
|
|
1728
1784
|
knownOne.delete(port)
|
|
1785
|
+
oneNativeApps.delete(port)
|
|
1729
1786
|
// a restarted process may be a different app — drop its captured name/icon.
|
|
1730
1787
|
enrichmentCache.delete(port)
|
|
1731
1788
|
localIconPathCache.delete(port)
|
|
@@ -1765,7 +1822,8 @@ export async function scanDevServers(
|
|
|
1765
1822
|
// "no widget".
|
|
1766
1823
|
await Promise.all(
|
|
1767
1824
|
results.map(async (result) => {
|
|
1768
|
-
|
|
1825
|
+
const nativeIdentity = oneNativeApps.get(result.port)
|
|
1826
|
+
if (result.framework !== 'one' && result.framework !== 'vxrn' && !nativeIdentity) {
|
|
1769
1827
|
result.widget = null
|
|
1770
1828
|
return
|
|
1771
1829
|
}
|
|
@@ -1780,10 +1838,13 @@ export async function scanDevServers(
|
|
|
1780
1838
|
result.widget ??= null
|
|
1781
1839
|
return
|
|
1782
1840
|
}
|
|
1783
|
-
|
|
1841
|
+
const scan =
|
|
1784
1842
|
scanRes.statusCode === 200 && scanRes.contentType?.includes('json')
|
|
1785
|
-
?
|
|
1843
|
+
? safeParseManifest(scanRes.body)
|
|
1786
1844
|
: null
|
|
1845
|
+
result.widget = nativeIdentity
|
|
1846
|
+
? oneNativeWidgetFromScan(scan, result, nativeIdentity)
|
|
1847
|
+
: configuredWidgetFromScan(scan, result.bundleUrl)
|
|
1787
1848
|
}),
|
|
1788
1849
|
)
|
|
1789
1850
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
1
2
|
import pixelmatch from 'pixelmatch'
|
|
2
3
|
import {
|
|
3
4
|
CONFORMANCE_FLAT_SHADE_FLATNESS,
|
|
@@ -123,13 +124,17 @@ export function compareConformancePixels(options: {
|
|
|
123
124
|
|
|
124
125
|
// identical frames need no shade or neighborhood search, but still use
|
|
125
126
|
// the same region and rounded-display exclusions for their pixel count.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
const identical = Buffer.from(
|
|
128
|
+
options.reference.buffer,
|
|
129
|
+
options.reference.byteOffset,
|
|
130
|
+
options.reference.byteLength,
|
|
131
|
+
).equals(
|
|
132
|
+
Buffer.from(
|
|
133
|
+
options.result.buffer,
|
|
134
|
+
options.result.byteOffset,
|
|
135
|
+
options.result.byteLength,
|
|
136
|
+
),
|
|
137
|
+
)
|
|
133
138
|
if (identical) {
|
|
134
139
|
options.diff?.fill(0)
|
|
135
140
|
const columns = Math.ceil(options.width)
|
package/src/menu.ts
CHANGED
|
@@ -1,107 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type SootSimMenuPlatform,
|
|
3
|
+
type SootSimScaleMode,
|
|
4
|
+
type SootSimAmbientGlowLevel,
|
|
5
|
+
SOOTSIM_AMBIENT_GLOW_LEVELS,
|
|
6
|
+
SOOTSIM_AMBIENT_GLOW_LABELS,
|
|
7
|
+
type SootSimStageBackground,
|
|
8
|
+
SOOTSIM_STAGE_BACKGROUNDS,
|
|
9
|
+
SOOTSIM_STAGE_BACKGROUND_LABELS,
|
|
10
|
+
type SootSimRenderResolution,
|
|
11
|
+
SOOTSIM_RENDER_RESOLUTION_LEVELS,
|
|
12
|
+
SOOTSIM_RENDER_RESOLUTION_LABELS,
|
|
13
|
+
type SootSimScreenEffect,
|
|
14
|
+
SOOTSIM_SCREEN_EFFECTS,
|
|
15
|
+
SOOTSIM_SCREEN_EFFECT_LABELS,
|
|
16
|
+
} from './menuContract'
|
|
1
17
|
import { rnxPublicBrand } from './public-brand'
|
|
2
18
|
|
|
3
19
|
// public sootsim menu protocol. this file is deliberately pure data and pure
|
|
4
20
|
// functions so hosts can import it without pulling the engine or canvaskit graph.
|
|
5
21
|
|
|
6
|
-
export
|
|
7
|
-
export const SOOTSIM_SHELL_COMMAND_NAME = 'sootsim:shell-command'
|
|
8
|
-
|
|
9
|
-
export type SootSimMenuPlatform = 'browser' | 'electron'
|
|
10
|
-
export type SootSimScaleMode = 'physical' | 'point' | 'pixel' | 'fit'
|
|
11
|
-
|
|
12
|
-
// ambient glow strength. one setting with three states rather than an on/off
|
|
13
|
-
// pair plus a separate strength: every host that carries the preference (the
|
|
14
|
-
// menus, the persisted setting, the ?ambientGlow= boot seed, the live
|
|
15
|
-
// set-ambient-glow push, electron's shared config) then carries exactly one
|
|
16
|
-
// value and cannot end up half-configured.
|
|
17
|
-
export type SootSimAmbientGlowLevel = 'off' | 'soft' | 'full'
|
|
18
|
-
export const SOOTSIM_AMBIENT_GLOW_LEVELS = ['off', 'soft', 'full'] as const
|
|
19
|
-
|
|
20
|
-
export function isSootSimAmbientGlowLevel(
|
|
21
|
-
value: unknown,
|
|
22
|
-
): value is SootSimAmbientGlowLevel {
|
|
23
|
-
return value === 'off' || value === 'soft' || value === 'full'
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const SOOTSIM_AMBIENT_GLOW_LABELS: Record<SootSimAmbientGlowLevel, string> = {
|
|
27
|
-
off: 'Off',
|
|
28
|
-
soft: 'Soft',
|
|
29
|
-
full: 'Full',
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// the stage behind the device: system follows the mac's appearance (white in
|
|
33
|
-
// light mode, black in dark), or one pinned color. one value per host, like
|
|
34
|
-
// the glow level above.
|
|
35
|
-
export type SootSimStageBackground = 'system' | 'light' | 'dark' | 'gray'
|
|
36
|
-
export const SOOTSIM_STAGE_BACKGROUNDS = ['system', 'light', 'dark', 'gray'] as const
|
|
37
|
-
|
|
38
|
-
export function isSootSimStageBackground(
|
|
39
|
-
value: unknown,
|
|
40
|
-
): value is SootSimStageBackground {
|
|
41
|
-
return value === 'system' || value === 'light' || value === 'dark' || value === 'gray'
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export const SOOTSIM_STAGE_BACKGROUND_LABELS: Record<SootSimStageBackground, string> = {
|
|
45
|
-
system: 'System',
|
|
46
|
-
light: 'Light',
|
|
47
|
-
dark: 'Dark',
|
|
48
|
-
gray: 'Gray',
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// render resolution: how many pixels peach renders per iphone point. one
|
|
52
|
-
// setting with three states, carried by every host exactly like the ambient
|
|
53
|
-
// glow level above: the menus, the persisted setting, the ?renderResolution=
|
|
54
|
-
// boot seed, the live set-render-resolution push, electron's shared config.
|
|
55
|
-
export type SootSimRenderResolution = 'high' | 'medium' | 'low'
|
|
56
|
-
export const SOOTSIM_RENDER_RESOLUTION_LEVELS = ['high', 'medium', 'low'] as const
|
|
57
|
-
|
|
58
|
-
// the installed desktop app presents the phone scaled to fit its window, so a
|
|
59
|
-
// fixed 2 px/pt canvas is browser-resampled and its icons read soft. its
|
|
60
|
-
// default is the level that tracks the pixels on screen; browsers and capture
|
|
61
|
-
// keep medium, whose 2 px/pt is the deterministic capture contract.
|
|
62
|
-
export const DESKTOP_RENDER_RESOLUTION: SootSimRenderResolution = 'low'
|
|
63
|
-
|
|
64
|
-
export function isSootSimRenderResolution(
|
|
65
|
-
value: unknown,
|
|
66
|
-
): value is SootSimRenderResolution {
|
|
67
|
-
return value === 'high' || value === 'medium' || value === 'low'
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export const SOOTSIM_RENDER_RESOLUTION_LABELS: Record<SootSimRenderResolution, string> = {
|
|
71
|
-
high: 'High',
|
|
72
|
-
medium: 'Medium',
|
|
73
|
-
low: 'Low',
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// screen effect: the real rendering, or one of the presentation looks the
|
|
77
|
-
// engine's wireframe store draws. 'wireframe' is the clean structural sketch;
|
|
78
|
-
// the others are its screen-space styles that ship as named effects.
|
|
79
|
-
export type SootSimScreenEffect = 'none' | 'wireframe' | 'blueprint' | 'panel' | 'green'
|
|
80
|
-
export const SOOTSIM_SCREEN_EFFECTS = [
|
|
81
|
-
'none',
|
|
82
|
-
'wireframe',
|
|
83
|
-
'blueprint',
|
|
84
|
-
'panel',
|
|
85
|
-
'green',
|
|
86
|
-
] as const
|
|
87
|
-
|
|
88
|
-
export function isSootSimScreenEffect(value: unknown): value is SootSimScreenEffect {
|
|
89
|
-
return (
|
|
90
|
-
value === 'none' ||
|
|
91
|
-
value === 'wireframe' ||
|
|
92
|
-
value === 'blueprint' ||
|
|
93
|
-
value === 'panel' ||
|
|
94
|
-
value === 'green'
|
|
95
|
-
)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export const SOOTSIM_SCREEN_EFFECT_LABELS: Record<SootSimScreenEffect, string> = {
|
|
99
|
-
none: 'None',
|
|
100
|
-
wireframe: 'Wireframe',
|
|
101
|
-
blueprint: 'Blueprint',
|
|
102
|
-
panel: 'Panel',
|
|
103
|
-
green: 'Green',
|
|
104
|
-
}
|
|
22
|
+
export * from './menuContract'
|
|
105
23
|
|
|
106
24
|
export type SootSimMenuItemDef =
|
|
107
25
|
| { type: 'separator' }
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// the values and event names the menu protocol carries. kept apart from the menu builder so a
|
|
2
|
+
// runtime that only validates a setting or names an event does not carry the menus at boot.
|
|
3
|
+
|
|
4
|
+
export const SOOTSIM_ENGINE_EVENT_NAME = 'sootsim:engine-event'
|
|
5
|
+
export const SOOTSIM_SHELL_COMMAND_NAME = 'sootsim:shell-command'
|
|
6
|
+
|
|
7
|
+
export type SootSimMenuPlatform = 'browser' | 'electron'
|
|
8
|
+
export type SootSimScaleMode = 'physical' | 'point' | 'pixel' | 'fit'
|
|
9
|
+
|
|
10
|
+
// ambient glow strength. one setting with three states rather than an on/off
|
|
11
|
+
// pair plus a separate strength: every host that carries the preference (the
|
|
12
|
+
// menus, the persisted setting, the ?ambientGlow= boot seed, the live
|
|
13
|
+
// set-ambient-glow push, electron's shared config) then carries exactly one
|
|
14
|
+
// value and cannot end up half-configured.
|
|
15
|
+
export type SootSimAmbientGlowLevel = 'off' | 'soft' | 'full'
|
|
16
|
+
export const SOOTSIM_AMBIENT_GLOW_LEVELS = ['off', 'soft', 'full'] as const
|
|
17
|
+
|
|
18
|
+
export function isSootSimAmbientGlowLevel(
|
|
19
|
+
value: unknown,
|
|
20
|
+
): value is SootSimAmbientGlowLevel {
|
|
21
|
+
return value === 'off' || value === 'soft' || value === 'full'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const SOOTSIM_AMBIENT_GLOW_LABELS: Record<SootSimAmbientGlowLevel, string> = {
|
|
25
|
+
off: 'Off',
|
|
26
|
+
soft: 'Soft',
|
|
27
|
+
full: 'Full',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// the stage behind the device: system follows the mac's appearance (white in
|
|
31
|
+
// light mode, black in dark), or one pinned color. one value per host, like
|
|
32
|
+
// the glow level above.
|
|
33
|
+
export type SootSimStageBackground = 'system' | 'light' | 'dark' | 'gray'
|
|
34
|
+
export const SOOTSIM_STAGE_BACKGROUNDS = ['system', 'light', 'dark', 'gray'] as const
|
|
35
|
+
|
|
36
|
+
export function isSootSimStageBackground(
|
|
37
|
+
value: unknown,
|
|
38
|
+
): value is SootSimStageBackground {
|
|
39
|
+
return value === 'system' || value === 'light' || value === 'dark' || value === 'gray'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const SOOTSIM_STAGE_BACKGROUND_LABELS: Record<SootSimStageBackground, string> = {
|
|
43
|
+
system: 'System',
|
|
44
|
+
light: 'Light',
|
|
45
|
+
dark: 'Dark',
|
|
46
|
+
gray: 'Gray',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// render resolution: how many pixels peach renders per iphone point. one
|
|
50
|
+
// setting with three states, carried by every host exactly like the ambient
|
|
51
|
+
// glow level above: the menus, the persisted setting, the ?renderResolution=
|
|
52
|
+
// boot seed, the live set-render-resolution push, electron's shared config.
|
|
53
|
+
export type SootSimRenderResolution = 'high' | 'medium' | 'low'
|
|
54
|
+
export const SOOTSIM_RENDER_RESOLUTION_LEVELS = ['high', 'medium', 'low'] as const
|
|
55
|
+
|
|
56
|
+
// the installed desktop app presents the phone scaled to fit its window, so a
|
|
57
|
+
// fixed 2 px/pt canvas is browser-resampled and its icons read soft. its
|
|
58
|
+
// default is the level that tracks the pixels on screen; browsers and capture
|
|
59
|
+
// keep medium, whose 2 px/pt is the deterministic capture contract.
|
|
60
|
+
export const DESKTOP_RENDER_RESOLUTION: SootSimRenderResolution = 'low'
|
|
61
|
+
|
|
62
|
+
export function isSootSimRenderResolution(
|
|
63
|
+
value: unknown,
|
|
64
|
+
): value is SootSimRenderResolution {
|
|
65
|
+
return value === 'high' || value === 'medium' || value === 'low'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const SOOTSIM_RENDER_RESOLUTION_LABELS: Record<SootSimRenderResolution, string> = {
|
|
69
|
+
high: 'High',
|
|
70
|
+
medium: 'Medium',
|
|
71
|
+
low: 'Low',
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// screen effect: the real rendering, or one of the presentation looks the
|
|
75
|
+
// engine's wireframe store draws. 'wireframe' is the clean structural sketch;
|
|
76
|
+
// the others are its screen-space styles that ship as named effects.
|
|
77
|
+
export type SootSimScreenEffect = 'none' | 'wireframe' | 'blueprint' | 'panel' | 'green'
|
|
78
|
+
export const SOOTSIM_SCREEN_EFFECTS = [
|
|
79
|
+
'none',
|
|
80
|
+
'wireframe',
|
|
81
|
+
'blueprint',
|
|
82
|
+
'panel',
|
|
83
|
+
'green',
|
|
84
|
+
] as const
|
|
85
|
+
|
|
86
|
+
export function isSootSimScreenEffect(value: unknown): value is SootSimScreenEffect {
|
|
87
|
+
return (
|
|
88
|
+
value === 'none' ||
|
|
89
|
+
value === 'wireframe' ||
|
|
90
|
+
value === 'blueprint' ||
|
|
91
|
+
value === 'panel' ||
|
|
92
|
+
value === 'green'
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const SOOTSIM_SCREEN_EFFECT_LABELS: Record<SootSimScreenEffect, string> = {
|
|
97
|
+
none: 'None',
|
|
98
|
+
wireframe: 'Wireframe',
|
|
99
|
+
blueprint: 'Blueprint',
|
|
100
|
+
panel: 'Panel',
|
|
101
|
+
green: 'Green',
|
|
102
|
+
}
|
|
@@ -61,14 +61,24 @@ export async function withTypeScriptSourceParser<T>(
|
|
|
61
61
|
if (!project) throw new Error('native TypeScript parser found no program')
|
|
62
62
|
// the program retains every input for resolution; transfer only the ASTs
|
|
63
63
|
// the caller analyzes across the native compiler's RPC boundary.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
const requestedPaths = Array.from(requestedSources)
|
|
65
|
+
for (let start = 0; start < requestedPaths.length; start += 64) {
|
|
66
|
+
const sources = await Promise.allSettled(
|
|
67
|
+
requestedPaths.slice(start, start + 64).map(async (path) => {
|
|
68
|
+
const virtualPath = sourcePaths.get(path)
|
|
69
|
+
if (!virtualPath)
|
|
70
|
+
throw new Error(`requested parser source is missing: ${path}`)
|
|
71
|
+
const sourceFile = await project.program.getSourceFile(virtualPath)
|
|
72
|
+
if (!sourceFile)
|
|
73
|
+
throw new Error(`native TypeScript parser did not return ${path}`)
|
|
74
|
+
return { path, sourceFile }
|
|
75
|
+
}),
|
|
76
|
+
)
|
|
77
|
+
for (const source of sources) {
|
|
78
|
+
if (source.status === 'rejected') throw source.reason
|
|
79
|
+
parsed.set(source.value.path, source.value.sourceFile)
|
|
80
|
+
projects.set(source.value.path, project)
|
|
81
|
+
}
|
|
72
82
|
}
|
|
73
83
|
return await analyze(parsed, projects)
|
|
74
84
|
} finally {
|
package/src/settingsContract.ts
CHANGED
|
@@ -2,14 +2,6 @@ import {
|
|
|
2
2
|
DEFAULT_PROFILER_CATEGORIES,
|
|
3
3
|
formatProfilerCategories,
|
|
4
4
|
} from '@peach/globals/profiler'
|
|
5
|
-
import {
|
|
6
|
-
SOOTSIM_AMBIENT_GLOW_LEVELS,
|
|
7
|
-
SOOTSIM_RENDER_RESOLUTION_LEVELS,
|
|
8
|
-
SOOTSIM_STAGE_BACKGROUNDS,
|
|
9
|
-
type SootSimAmbientGlowLevel,
|
|
10
|
-
type SootSimRenderResolution,
|
|
11
|
-
type SootSimStageBackground,
|
|
12
|
-
} from 'peachsim/menu'
|
|
13
5
|
import {
|
|
14
6
|
type DeviceModel,
|
|
15
7
|
type DevicePosture,
|
|
@@ -19,6 +11,14 @@ import {
|
|
|
19
11
|
DYNAMIC_WINDOW_LIMITS,
|
|
20
12
|
devices,
|
|
21
13
|
} from './device-catalog.ts'
|
|
14
|
+
import {
|
|
15
|
+
SOOTSIM_AMBIENT_GLOW_LEVELS,
|
|
16
|
+
SOOTSIM_RENDER_RESOLUTION_LEVELS,
|
|
17
|
+
SOOTSIM_STAGE_BACKGROUNDS,
|
|
18
|
+
type SootSimAmbientGlowLevel,
|
|
19
|
+
type SootSimRenderResolution,
|
|
20
|
+
type SootSimStageBackground,
|
|
21
|
+
} from './menuContract'
|
|
22
22
|
import { DEFAULT_REACT_NATIVE_CONFIGURATION } from './reactNativeConfigurationContract.ts'
|
|
23
23
|
import {
|
|
24
24
|
BACKGROUND_PRESET_NAMES,
|
package/src/swift-host.ts
CHANGED
|
@@ -132,8 +132,10 @@ import {
|
|
|
132
132
|
} from './swiftContract'
|
|
133
133
|
import {
|
|
134
134
|
enclosingXcodeWorkspace,
|
|
135
|
+
expandXcodeBuildSetting,
|
|
135
136
|
readXcodeAppTarget,
|
|
136
137
|
resolveXcodeSwiftPackages,
|
|
138
|
+
xcodeFileListEntries,
|
|
137
139
|
type XcodeAppTarget,
|
|
138
140
|
type XcodeModuleTarget,
|
|
139
141
|
} from './xcode-project'
|
|
@@ -2312,11 +2314,34 @@ export function buildSwiftBundleFiles(
|
|
|
2312
2314
|
CONTENTS_FOLDER_PATH: wrapper,
|
|
2313
2315
|
UNLOCALIZED_RESOURCES_FOLDER_PATH: wrapper,
|
|
2314
2316
|
INFOPLIST_PATH: `${wrapper}/Info.plist`,
|
|
2315
|
-
SCRIPT_INPUT_FILE_COUNT:
|
|
2316
|
-
SCRIPT_INPUT_FILE_LIST_COUNT:
|
|
2317
|
+
SCRIPT_INPUT_FILE_COUNT: String(phase.inputPaths.length),
|
|
2318
|
+
SCRIPT_INPUT_FILE_LIST_COUNT: String(phase.inputFileListPaths.length),
|
|
2317
2319
|
SCRIPT_OUTPUT_FILE_COUNT: '0',
|
|
2318
2320
|
SCRIPT_OUTPUT_FILE_LIST_COUNT: '0',
|
|
2319
2321
|
}
|
|
2322
|
+
const inputPath = (raw: string) => {
|
|
2323
|
+
const file = path.resolve(
|
|
2324
|
+
phase.directory,
|
|
2325
|
+
expandXcodeBuildSetting(raw, (key) => env[key] ?? ''),
|
|
2326
|
+
)
|
|
2327
|
+
if (!fs.existsSync(file))
|
|
2328
|
+
throw new Error(
|
|
2329
|
+
`${xcode.app.name}: Run Script "${phase.name}" missing input ${file}`,
|
|
2330
|
+
)
|
|
2331
|
+
if (!file.startsWith(`${dir}${path.sep}`))
|
|
2332
|
+
watched.push(path.relative(packagePath, file).split(path.sep).join('/'))
|
|
2333
|
+
return file
|
|
2334
|
+
}
|
|
2335
|
+
phase.inputPaths.forEach((raw, index) => {
|
|
2336
|
+
env[`SCRIPT_INPUT_FILE_${index}`] = inputPath(raw)
|
|
2337
|
+
})
|
|
2338
|
+
phase.inputFileListPaths.forEach((raw, index) => {
|
|
2339
|
+
const source = inputPath(raw)
|
|
2340
|
+
const entries = xcodeFileListEntries(source, inputPath)
|
|
2341
|
+
const expanded = path.join(dir, `Input-${index}.xcfilelist`)
|
|
2342
|
+
fs.writeFileSync(expanded, `${entries.join('\n')}\n`)
|
|
2343
|
+
env[`SCRIPT_INPUT_FILE_LIST_${index}`] = expanded
|
|
2344
|
+
})
|
|
2320
2345
|
try {
|
|
2321
2346
|
const [shell, ...args] = phase.command
|
|
2322
2347
|
execFileSync(shell, args, {
|
package/src/vite-plugin-one.ts
CHANGED
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
} from './runtime-assets'
|
|
47
47
|
import { ensureRuntimePackFor } from './runtime-packs'
|
|
48
48
|
import { SWIFT_HMR_PATH, SWIFT_PACKAGE_PATH } from './swift-submit'
|
|
49
|
+
import { privateSwiftDistributionRoot } from './swift-toolchain'
|
|
49
50
|
import { swiftViteLifetime } from './swift-vite-lifetime'
|
|
50
51
|
import type { OneWidgetPackage } from './oneWidgetContract'
|
|
51
52
|
import type { PeachPluginOptions } from './vite'
|
|
@@ -235,6 +236,7 @@ export function peachPlugin(options: PeachPluginOptions = {}): Plugin {
|
|
|
235
236
|
// npm install has none (null), and its packages compile on Contrast.
|
|
236
237
|
let swiftModuleRoot: string | null | undefined
|
|
237
238
|
const moduleRootFor = (): string | undefined => {
|
|
239
|
+
if (privateSwiftDistributionRoot()) return undefined
|
|
238
240
|
if (swiftModuleRoot === undefined) {
|
|
239
241
|
// src/ or dist-lib/ → .. is peachsim; the module sits beside it
|
|
240
242
|
const moduleRoot = path.resolve(
|
package/src/vite-plugin.ts
CHANGED
|
@@ -2542,23 +2542,23 @@ function externalAppResolvePlugin(
|
|
|
2542
2542
|
// convert require() calls and handle JSX in .js files from external app sources
|
|
2543
2543
|
function externalAppTransformPlugin(isAppSource: (p: string) => boolean): Plugin {
|
|
2544
2544
|
const defines: Record<string, string> = {}
|
|
2545
|
+
let isBuild = false
|
|
2545
2546
|
return {
|
|
2546
2547
|
name: 'sootsim-external-app-transform',
|
|
2547
2548
|
enforce: 'pre',
|
|
2548
2549
|
configResolved(config) {
|
|
2550
|
+
isBuild = config.command === 'build'
|
|
2549
2551
|
for (const [name, value] of Object.entries(config.define ?? {}))
|
|
2550
2552
|
defines[name] = typeof value === 'string' ? value : JSON.stringify(value)
|
|
2551
2553
|
},
|
|
2552
2554
|
transform: {
|
|
2553
2555
|
filter: { code: /require\(|</ },
|
|
2554
2556
|
async handler(code, id) {
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
if (!isAppSource(id) && !/\/node_modules\/expo\/(?:src|build)\/winter\//.test(id))
|
|
2558
|
-
return null
|
|
2557
|
+
const isExpoWinter = /\/node_modules\/expo\/(?:src|build)\/winter\//.test(id)
|
|
2558
|
+
if (!isAppSource(id) && !isExpoWinter) return null
|
|
2559
2559
|
|
|
2560
|
-
// prune
|
|
2561
|
-
let result =
|
|
2560
|
+
// prune disabled native branches before resolving their requires.
|
|
2561
|
+
let result = isExpoWinter
|
|
2562
2562
|
? minifySync(
|
|
2563
2563
|
stripViteQuery(id),
|
|
2564
2564
|
transformSync(stripViteQuery(id), code, { define: defines }).code,
|
|
@@ -2567,9 +2567,10 @@ function externalAppTransformPlugin(isAppSource: (p: string) => boolean): Plugin
|
|
|
2567
2567
|
: code
|
|
2568
2568
|
let imports = ''
|
|
2569
2569
|
let importCounter = 0
|
|
2570
|
-
let hasChanges =
|
|
2570
|
+
let hasChanges = isExpoWinter
|
|
2571
2571
|
|
|
2572
|
-
|
|
2572
|
+
// rolldown keeps winter's lazy requires at their call sites in builds.
|
|
2573
|
+
if (code.includes('require(') && !(isBuild && isExpoWinter)) {
|
|
2573
2574
|
const replacements: Array<{ start: number; end: number; varName: string }> = []
|
|
2574
2575
|
const seenPaths = new Map<string, string>()
|
|
2575
2576
|
const pending: unknown[] = [parseSync(stripViteQuery(id), result).program]
|
package/src/xcode-project.ts
CHANGED
|
@@ -180,6 +180,8 @@ export interface XcodeAppTarget extends XcodeModuleTarget {
|
|
|
180
180
|
command: [string, ...string[]]
|
|
181
181
|
directory: string
|
|
182
182
|
environment: Record<string, string>
|
|
183
|
+
inputPaths: string[]
|
|
184
|
+
inputFileListPaths: string[]
|
|
183
185
|
}[]
|
|
184
186
|
appIconName: string
|
|
185
187
|
/** the app project PBXProject developmentRegion, xcode's "en" when it declares none. */
|
|
@@ -890,6 +892,8 @@ function compilationConditions(settings: (key: string) => string): string[] {
|
|
|
890
892
|
]
|
|
891
893
|
}
|
|
892
894
|
|
|
895
|
+
export { expand as expandXcodeBuildSetting }
|
|
896
|
+
|
|
893
897
|
function expand(value: string, lookup: (name: string) => string): string {
|
|
894
898
|
return value.replace(
|
|
895
899
|
/\$(?:\(([A-Za-z_][A-Za-z0-9_]*)((?::[^)]*)?)\)|\{([A-Za-z_][A-Za-z0-9_]*)((?::[^}]*)?)\}|([A-Za-z_][A-Za-z0-9_]*))/g,
|
|
@@ -1013,6 +1017,8 @@ function shellCopies(
|
|
|
1013
1017
|
}
|
|
1014
1018
|
|
|
1015
1019
|
// an xcfilelist names one path per line; blank lines and comments are skipped.
|
|
1020
|
+
export { fileListEntries as xcodeFileListEntries }
|
|
1021
|
+
|
|
1016
1022
|
function fileListEntries(file: string, absolute: (raw: string) => string): string[] {
|
|
1017
1023
|
return fs
|
|
1018
1024
|
.readFileSync(file, 'utf8')
|
|
@@ -2851,8 +2857,6 @@ export function readXcodeAppTarget(
|
|
|
2851
2857
|
const phase = app.project.object(id)
|
|
2852
2858
|
if (
|
|
2853
2859
|
phase?.isa !== 'PBXShellScriptBuildPhase' ||
|
|
2854
|
-
list(phase.inputPaths).length ||
|
|
2855
|
-
list(phase.inputFileListPaths).length ||
|
|
2856
2860
|
list(phase.outputPaths).length ||
|
|
2857
2861
|
list(phase.outputFileListPaths).length
|
|
2858
2862
|
)
|
|
@@ -2864,6 +2868,8 @@ export function readXcodeAppTarget(
|
|
|
2864
2868
|
str(phase.shellScript) ?? '',
|
|
2865
2869
|
),
|
|
2866
2870
|
directory: app.project.projectDir,
|
|
2871
|
+
inputPaths: list(phase.inputPaths),
|
|
2872
|
+
inputFileListPaths: list(phase.inputFileListPaths),
|
|
2867
2873
|
environment: Object.fromEntries(
|
|
2868
2874
|
[...settingKeys]
|
|
2869
2875
|
.filter((key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|