rnxsim 0.1.519 → 0.1.520
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/cloud-client.ts +419 -287
- package/cli/cloud-dispatch.ts +2 -21
- package/cli/cloud-session.ts +31 -85
- package/cli/commands/box.ts +15 -11
- package/cli/commands/inspect.ts +34 -1
- package/cli/commands/platform.ts +184 -232
- package/cli/main.ts +10 -2
- package/cli/outbound-endpoints.ts +21 -6
- 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.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/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +112 -39
- 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 +43 -117
- 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 +30 -1
- package/dist-lib/menu.mjs +27 -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 +41 -4
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +138 -164
- package/dist-lib/swift.cjs +674 -12
- package/dist-lib/vite.cjs +26 -3
- package/package.json +1 -1
- package/scripts/dev-server-scanner.ts +72 -5
- package/src/cloud-contract.ts +7 -1
- package/src/cloud.ts +11 -4
- package/src/connect.ts +1 -1
- package/src/menu.ts +44 -0
- package/src/vite-plugin-one.ts +12 -0
- package/src/vite-plugin-swift.ts +17 -5
package/dist-lib/vite.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.520 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -762,12 +762,19 @@ var import_node_fs3 = __toESM(require("node:fs"), 1);
|
|
|
762
762
|
var import_node_os2 = __toESM(require("node:os"), 1);
|
|
763
763
|
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
764
764
|
var import_node_util = require("node:util");
|
|
765
|
+
|
|
766
|
+
// ../sootsim-swift/toolchain.json
|
|
767
|
+
var toolchain_default = {
|
|
768
|
+
release: "swift-6.3-RELEASE"
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
// src/vite-plugin-swift.ts
|
|
765
772
|
var run = (0, import_node_util.promisify)(import_node_child_process2.execFile);
|
|
766
773
|
var TOOLCHAIN_BIN = import_node_path4.default.join(
|
|
767
774
|
import_node_os2.default.homedir(),
|
|
768
|
-
|
|
775
|
+
`Library/Developer/Toolchains/${toolchain_default.release}.xctoolchain/usr/bin`
|
|
769
776
|
);
|
|
770
|
-
var WASM_SDK =
|
|
777
|
+
var WASM_SDK = `${toolchain_default.release}_wasm`;
|
|
771
778
|
var WASM_TRIPLE = "wasm32-unknown-wasip1";
|
|
772
779
|
var ARTIFACT_PATH = "/__rnx/swift/";
|
|
773
780
|
var VIRTUAL_PREFIX = "\0rnx-swift:";
|
|
@@ -878,6 +885,11 @@ function swiftPlugin(options = {}) {
|
|
|
878
885
|
name: "rnx-swift",
|
|
879
886
|
enforce: "pre",
|
|
880
887
|
async resolveId(source, importer) {
|
|
888
|
+
if (source.startsWith(VIRTUAL_PREFIX)) return source;
|
|
889
|
+
if (source.startsWith("/@id/")) {
|
|
890
|
+
const decoded = source.slice("/@id/".length).replace(/^__x00__/, "\0");
|
|
891
|
+
return decoded.startsWith(VIRTUAL_PREFIX) ? decoded : null;
|
|
892
|
+
}
|
|
881
893
|
if (!source.endsWith(".swift")) return null;
|
|
882
894
|
const resolved = await this.resolve(source, importer, { skipSelf: true });
|
|
883
895
|
if (!resolved) throw new Error(`cannot resolve swift source ${source}`);
|
|
@@ -938,6 +950,17 @@ function rnxPlugin(options = {}) {
|
|
|
938
950
|
const prefix = options.prefix || "/__soot";
|
|
939
951
|
return {
|
|
940
952
|
name: "sootsim-one",
|
|
953
|
+
// use-latest-callback ships a CJS main (module.exports, no default) with
|
|
954
|
+
// an esm.mjs shim that re-imports it. react-native-bottom-tabs
|
|
955
|
+
// default-imports it from source served live, so vite hands the browser
|
|
956
|
+
// the raw CJS file and the import fails with 'does not provide an export
|
|
957
|
+
// named default' before anything mounts. forcing it through dep
|
|
958
|
+
// optimization lets esbuild's CJS interop answer the default import, and
|
|
959
|
+
// every bare import of it (including the nested copy's) resolves to the
|
|
960
|
+
// one pre-bundled instance.
|
|
961
|
+
config() {
|
|
962
|
+
return { optimizeDeps: { include: ["use-latest-callback"] } };
|
|
963
|
+
},
|
|
941
964
|
configureServer(server) {
|
|
942
965
|
if (options.enabled === false) return;
|
|
943
966
|
const bundleUrl = options.bundleUrl || "/node_modules/one/metro-entry.bundle?platform=ios&dev=true&minify=false";
|
package/package.json
CHANGED
|
@@ -94,6 +94,10 @@ export interface DiscoveredServer {
|
|
|
94
94
|
lastSeen: number
|
|
95
95
|
iconUrl?: string
|
|
96
96
|
iconPath?: string
|
|
97
|
+
/** the app's own brand colour from its manifest, used as the home tile's
|
|
98
|
+
* background so the tile reads as that app before its icon decodes and
|
|
99
|
+
* when the icon is missing entirely. */
|
|
100
|
+
primaryColor?: string
|
|
97
101
|
bundleId?: string
|
|
98
102
|
settingsBundle?: readonly AppSettingsSpecifier[]
|
|
99
103
|
settingsStorageScopeUrl?: string
|
|
@@ -594,6 +598,43 @@ function safeParseManifest(body: string): Record<string, unknown> | null {
|
|
|
594
598
|
}
|
|
595
599
|
}
|
|
596
600
|
|
|
601
|
+
// a manifest icon path ("./assets/app-icons/x.png") is relative to the project
|
|
602
|
+
// root. metro's asset route prefixes the whole relative path with /assets/; a
|
|
603
|
+
// statically-served project answers at the literal path. the manifest says
|
|
604
|
+
// nothing about which, so the two candidates are probed once per port and the
|
|
605
|
+
// winner cached. keyed by port and cleared with the rest of that port's state
|
|
606
|
+
// when the process disappears.
|
|
607
|
+
const localIconPathCache = new Map<number, string>()
|
|
608
|
+
const localIconProbeInFlight = new Set<number>()
|
|
609
|
+
|
|
610
|
+
function metroAssetPath(rawIcon: string): string {
|
|
611
|
+
return `/assets/${rawIcon.replace(/^\.?\//, '')}`
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function staticAssetPath(rawIcon: string): string {
|
|
615
|
+
return `/${rawIcon.replace(/^\.?\//, '')}`
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// probe both shapes and cache whichever the server actually serves. resolves
|
|
619
|
+
// nothing when neither answers — the tile falls back to the app's brand colour.
|
|
620
|
+
async function resolveLocalIconPath(
|
|
621
|
+
hostname: string,
|
|
622
|
+
port: number,
|
|
623
|
+
rawIcon: string,
|
|
624
|
+
): Promise<void> {
|
|
625
|
+
const candidates = [metroAssetPath(rawIcon), staticAssetPath(rawIcon)]
|
|
626
|
+
for (const candidate of candidates) {
|
|
627
|
+
const res = await httpGet(hostname, port, candidate, 'HEAD', ENRICH_TIMEOUT_MS)
|
|
628
|
+
if (res?.statusCode === 200) {
|
|
629
|
+
localIconPathCache.set(port, candidate)
|
|
630
|
+
// the identified result caches as a strong entry and won't re-probe on
|
|
631
|
+
// its own; drop it so the next scan republishes with the working url.
|
|
632
|
+
portCache.delete(port)
|
|
633
|
+
return
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
597
638
|
// fold expo manifest fields (name, icon, bundleId, launchAsset) into a result.
|
|
598
639
|
// the manifest body is always the GET / response we already have from probePort,
|
|
599
640
|
// so this never issues another http request.
|
|
@@ -607,6 +648,7 @@ function applyManifest(
|
|
|
607
648
|
const manifest = JSON.parse(manifestRes.body)
|
|
608
649
|
const client = manifest?.extra?.expoClient || manifest?.extra || {}
|
|
609
650
|
if (client.name) result.projectName = client.name
|
|
651
|
+
if (typeof client.primaryColor === 'string') result.primaryColor = client.primaryColor
|
|
610
652
|
if (client.ios?.bundleIdentifier) result.bundleId = client.ios.bundleIdentifier
|
|
611
653
|
if (result.framework === 'metro' && client.sdkVersion) result.framework = 'expo'
|
|
612
654
|
// preserve a confirmed direct one/vxrn metro-entry bundle. expo manifests
|
|
@@ -645,11 +687,16 @@ function applyManifest(
|
|
|
645
687
|
resolvedIconUrl = rawIcon
|
|
646
688
|
}
|
|
647
689
|
} else {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
690
|
+
// a manifest icon path is relative to the project root, and the two
|
|
691
|
+
// server shapes we meet serve it at different URLs: metro's asset route
|
|
692
|
+
// is `/assets/` + that whole relative path (so `assets/x.png` lands at
|
|
693
|
+
// `/assets/assets/x.png`), while a statically-served project answers at
|
|
694
|
+
// the literal path. neither shape can be derived from the manifest, so
|
|
695
|
+
// resolveLocalIconPath probes the server once and caches the answer;
|
|
696
|
+
// until it lands we publish the metro form, which is the common case.
|
|
697
|
+
const base = buildLoopbackProbeBaseUrl('http:', manifestRes.hostname, result.port)
|
|
698
|
+
const assetPath = localIconPathCache.get(result.port) ?? metroAssetPath(rawIcon)
|
|
699
|
+
const localUrl = `${base}${assetPath}`
|
|
653
700
|
resolvedIconUrl = buildIconProxyUrl ? buildIconProxyUrl(localUrl) : localUrl
|
|
654
701
|
}
|
|
655
702
|
if (resolvedIconUrl) {
|
|
@@ -700,6 +747,21 @@ interface ManifestEnrichment {
|
|
|
700
747
|
const enrichmentCache = new Map<number, ManifestEnrichment>()
|
|
701
748
|
const enrichmentInFlight = new Set<number>()
|
|
702
749
|
|
|
750
|
+
// a result that carries a relative icon path has not had its serving shape
|
|
751
|
+
// confirmed yet. probe once per port; applyManifest reads the cached answer.
|
|
752
|
+
function ensureLocalIconPathResolved(result: DiscoveredServer): void {
|
|
753
|
+
const rawIcon = result.iconPath
|
|
754
|
+
if (!rawIcon) return
|
|
755
|
+
if (rawIcon.startsWith('http://') || rawIcon.startsWith('https://')) return
|
|
756
|
+
if (localIconPathCache.has(result.port)) return
|
|
757
|
+
if (localIconProbeInFlight.has(result.port)) return
|
|
758
|
+
localIconProbeInFlight.add(result.port)
|
|
759
|
+
const hostname = normalizeHostname(new URL(result.bundleUrl).hostname)
|
|
760
|
+
void resolveLocalIconPath(hostname, result.port, rawIcon).finally(() =>
|
|
761
|
+
localIconProbeInFlight.delete(result.port),
|
|
762
|
+
)
|
|
763
|
+
}
|
|
764
|
+
|
|
703
765
|
// apply already-captured name/icon, or kick off a single background manifest
|
|
704
766
|
// fetch to capture them. a no-op once the in-race manifest already named the
|
|
705
767
|
// server, so it's safe to wrap every identified return.
|
|
@@ -707,6 +769,7 @@ function enrichWithManifest(
|
|
|
707
769
|
result: DiscoveredServer,
|
|
708
770
|
buildIconProxyUrl?: (externalUrl: string) => string,
|
|
709
771
|
): DiscoveredServer {
|
|
772
|
+
ensureLocalIconPathResolved(result)
|
|
710
773
|
if (result.projectName) return result
|
|
711
774
|
|
|
712
775
|
const cached = enrichmentCache.get(result.port)
|
|
@@ -1157,6 +1220,9 @@ export async function scanDevServers(
|
|
|
1157
1220
|
for (const p of [...enrichmentCache.keys()]) {
|
|
1158
1221
|
if (!currentPorts.has(p)) enrichmentCache.delete(p)
|
|
1159
1222
|
}
|
|
1223
|
+
for (const p of [...localIconPathCache.keys()]) {
|
|
1224
|
+
if (!currentPorts.has(p)) localIconPathCache.delete(p)
|
|
1225
|
+
}
|
|
1160
1226
|
|
|
1161
1227
|
const results: DiscoveredServer[] = []
|
|
1162
1228
|
const toProbe: ListeningProcess[] = []
|
|
@@ -1179,6 +1245,7 @@ export async function scanDevServers(
|
|
|
1179
1245
|
knownOne.delete(port)
|
|
1180
1246
|
// a restarted process may be a different app — drop its captured name/icon.
|
|
1181
1247
|
enrichmentCache.delete(port)
|
|
1248
|
+
localIconPathCache.delete(port)
|
|
1182
1249
|
}
|
|
1183
1250
|
toProbe.push({ port, pid })
|
|
1184
1251
|
}
|
package/src/cloud-contract.ts
CHANGED
|
@@ -6,7 +6,13 @@ export const RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024
|
|
|
6
6
|
export const RNX_CLOUD_TEST_FILES_MAX_COUNT = 256
|
|
7
7
|
export const RNX_CLOUD_TEST_FILES_MAX_BYTES = 500_000
|
|
8
8
|
|
|
9
|
-
export type RnxCloudCommandScope =
|
|
9
|
+
export type RnxCloudCommandScope =
|
|
10
|
+
| 'read'
|
|
11
|
+
| 'drive'
|
|
12
|
+
| 'reset'
|
|
13
|
+
| 'media'
|
|
14
|
+
| 'debug'
|
|
15
|
+
| 'watch'
|
|
10
16
|
|
|
11
17
|
const RNX_CLOUD_COMMAND_SCOPE_SET = new Set<string>([
|
|
12
18
|
'read',
|
package/src/cloud.ts
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
// public surface.
|
|
10
10
|
|
|
11
11
|
import { isLoopbackHost } from './backend-origin'
|
|
12
|
-
import type { SimStateOptions } from './bridge-contract'
|
|
13
12
|
import { isRnxScreenCapture, type RnxScreenCapture } from './capture-contract'
|
|
14
13
|
import {
|
|
15
14
|
isRnxCloudCommandScope,
|
|
@@ -27,6 +26,7 @@ import {
|
|
|
27
26
|
type RnxCloudScopedToken,
|
|
28
27
|
} from './cloud-contract'
|
|
29
28
|
import { rnxPublicBrand } from './public-brand'
|
|
29
|
+
import type { SimStateOptions } from './bridge-contract'
|
|
30
30
|
|
|
31
31
|
export {
|
|
32
32
|
isRnxCloudCommandScope,
|
|
@@ -436,7 +436,9 @@ export class RemoteSimulator {
|
|
|
436
436
|
reset: () => this.sendCommand({ type: 'reset' }),
|
|
437
437
|
state: (options) =>
|
|
438
438
|
this.sendCommand(
|
|
439
|
-
options === undefined
|
|
439
|
+
options === undefined
|
|
440
|
+
? { type: 'state' }
|
|
441
|
+
: { type: 'state', stateOptions: options },
|
|
440
442
|
),
|
|
441
443
|
}
|
|
442
444
|
}
|
|
@@ -653,7 +655,10 @@ function readConnectClaim(value: unknown): RnxCloudClaim | null {
|
|
|
653
655
|
return readClaim(value)
|
|
654
656
|
}
|
|
655
657
|
|
|
656
|
-
function readConnectScopes(
|
|
658
|
+
function readConnectScopes(
|
|
659
|
+
value: unknown,
|
|
660
|
+
token: string,
|
|
661
|
+
): readonly RnxCloudCommandScope[] {
|
|
657
662
|
// the full per-simulator token carries every scope except debug, which the
|
|
658
663
|
// service already guarantees. a scoped token learns its verbs from the get
|
|
659
664
|
// it just made, so a verb outside them is refused before any request.
|
|
@@ -678,7 +683,9 @@ function readConnectScopes(value: unknown, token: string): readonly RnxCloudComm
|
|
|
678
683
|
// attach to a simulator another process created. one get proves the token,
|
|
679
684
|
// and the simulator it returns drives with that token's scopes, takes no
|
|
680
685
|
// claim, and never steals one.
|
|
681
|
-
export async function connect(
|
|
686
|
+
export async function connect(
|
|
687
|
+
input: ConnectRemoteSimulatorInput,
|
|
688
|
+
): Promise<RemoteSimulator> {
|
|
682
689
|
if (!input.id) throw new Error('RNX Cloud connect needs a simulator id')
|
|
683
690
|
if (!input.token) throw new Error('RNX Cloud connect needs a token')
|
|
684
691
|
const origin = resolveOrigin(input.origin)
|
package/src/connect.ts
CHANGED
|
@@ -67,7 +67,7 @@ export async function connect(options: ConnectOptions = {}): Promise<SimClient>
|
|
|
67
67
|
const bridge = createCloudBridgeForParsed(target)
|
|
68
68
|
if (!bridge)
|
|
69
69
|
throw new SimConnectError('remote simulator session disappeared while connecting')
|
|
70
|
-
return new SimClient(bridge, cloud.
|
|
70
|
+
return new SimClient(bridge, cloud.simId, {
|
|
71
71
|
commandTimeoutMs: options.timeoutMs ?? 15_000,
|
|
72
72
|
})
|
|
73
73
|
}
|
package/src/menu.ts
CHANGED
|
@@ -29,6 +29,28 @@ export const SOOTSIM_AMBIENT_GLOW_LABELS: Record<SootSimAmbientGlowLevel, string
|
|
|
29
29
|
full: 'Full',
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// which ios home screen the simulator portrays. the home screen is authored
|
|
33
|
+
// twice (the flat ios 26 home and the ios 27 home measured off apple's art),
|
|
34
|
+
// so the era is one value every host carries: the menus, the persisted
|
|
35
|
+
// setting, the ?homeScreen= boot seed, the live set-home-screen push and
|
|
36
|
+
// electron's shared config.
|
|
37
|
+
export type SootSimHomeScreenVersion = 'ios26' | 'ios27'
|
|
38
|
+
export const SOOTSIM_HOME_SCREEN_VERSIONS = ['ios26', 'ios27'] as const
|
|
39
|
+
|
|
40
|
+
export function isSootSimHomeScreenVersion(
|
|
41
|
+
value: unknown,
|
|
42
|
+
): value is SootSimHomeScreenVersion {
|
|
43
|
+
return value === 'ios26' || value === 'ios27'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const SOOTSIM_HOME_SCREEN_VERSION_LABELS: Record<
|
|
47
|
+
SootSimHomeScreenVersion,
|
|
48
|
+
string
|
|
49
|
+
> = {
|
|
50
|
+
ios26: 'iOS 26',
|
|
51
|
+
ios27: 'iOS 27',
|
|
52
|
+
}
|
|
53
|
+
|
|
32
54
|
export type SootSimMenuItemDef =
|
|
33
55
|
| { type: 'separator' }
|
|
34
56
|
| {
|
|
@@ -56,6 +78,11 @@ export type SootSimMenuDef = {
|
|
|
56
78
|
|
|
57
79
|
export interface SootSimMenuState {
|
|
58
80
|
currentDevice: string
|
|
81
|
+
homeScreenVersion: SootSimHomeScreenVersion
|
|
82
|
+
// whether this surface is a contributor development surface. unreleased
|
|
83
|
+
// simulator surfaces (the iPhone Duo and its home-screen era switch) are
|
|
84
|
+
// offered here and nowhere a user can reach.
|
|
85
|
+
developmentMode: boolean
|
|
59
86
|
showBezels: boolean
|
|
60
87
|
showMenuBar: boolean
|
|
61
88
|
showRail: boolean
|
|
@@ -455,6 +482,23 @@ function windowMenu(state: SootSimMenuState): SootSimMenuDef {
|
|
|
455
482
|
actionPayload: level,
|
|
456
483
|
})),
|
|
457
484
|
},
|
|
485
|
+
// both eras stay selectable while the ios 27 home surfaces land one at a
|
|
486
|
+
// time, and the device's own os picks the starting value. the switch is
|
|
487
|
+
// the iPhone Duo's, so it is offered exactly where the Duo is.
|
|
488
|
+
...(state.developmentMode
|
|
489
|
+
? [
|
|
490
|
+
{
|
|
491
|
+
label: 'Home Screen',
|
|
492
|
+
items: SOOTSIM_HOME_SCREEN_VERSIONS.map((version) => ({
|
|
493
|
+
label: SOOTSIM_HOME_SCREEN_VERSION_LABELS[version],
|
|
494
|
+
type: 'radio' as const,
|
|
495
|
+
checked: state.homeScreenVersion === version,
|
|
496
|
+
action: 'set-home-screen',
|
|
497
|
+
actionPayload: version,
|
|
498
|
+
})),
|
|
499
|
+
},
|
|
500
|
+
]
|
|
501
|
+
: []),
|
|
458
502
|
{
|
|
459
503
|
label: 'Wireframe',
|
|
460
504
|
type: 'checkbox',
|
package/src/vite-plugin-one.ts
CHANGED
|
@@ -46,6 +46,18 @@ export function rnxPlugin(options: RnxPluginOptions = {}): Plugin {
|
|
|
46
46
|
return {
|
|
47
47
|
name: 'sootsim-one',
|
|
48
48
|
|
|
49
|
+
// use-latest-callback ships a CJS main (module.exports, no default) with
|
|
50
|
+
// an esm.mjs shim that re-imports it. react-native-bottom-tabs
|
|
51
|
+
// default-imports it from source served live, so vite hands the browser
|
|
52
|
+
// the raw CJS file and the import fails with 'does not provide an export
|
|
53
|
+
// named default' before anything mounts. forcing it through dep
|
|
54
|
+
// optimization lets esbuild's CJS interop answer the default import, and
|
|
55
|
+
// every bare import of it (including the nested copy's) resolves to the
|
|
56
|
+
// one pre-bundled instance.
|
|
57
|
+
config() {
|
|
58
|
+
return { optimizeDeps: { include: ['use-latest-callback'] } }
|
|
59
|
+
},
|
|
60
|
+
|
|
49
61
|
configureServer(server) {
|
|
50
62
|
if (options.enabled === false) return
|
|
51
63
|
const bundleUrl =
|
package/src/vite-plugin-swift.ts
CHANGED
|
@@ -16,18 +16,20 @@ import fs from 'node:fs'
|
|
|
16
16
|
import os from 'node:os'
|
|
17
17
|
import path from 'node:path'
|
|
18
18
|
import { promisify } from 'node:util'
|
|
19
|
+
import toolchain from '../../sootsim-swift/toolchain.json'
|
|
19
20
|
import type { Plugin, ViteDevServer } from 'vite'
|
|
20
21
|
|
|
21
22
|
const run = promisify(execFile)
|
|
22
23
|
|
|
23
|
-
// the
|
|
24
|
-
//
|
|
25
|
-
//
|
|
24
|
+
// the swift.org release scripts/build.sh builds with, from the one place it is
|
|
25
|
+
// named: the toolchain is <release>.xctoolchain and its matching wasm sdk is
|
|
26
|
+
// <release>_wasm. xcode's swift has no wasm backend, so this toolchain is the
|
|
27
|
+
// only one that can build the target.
|
|
26
28
|
const TOOLCHAIN_BIN = path.join(
|
|
27
29
|
os.homedir(),
|
|
28
|
-
|
|
30
|
+
`Library/Developer/Toolchains/${toolchain.release}.xctoolchain/usr/bin`,
|
|
29
31
|
)
|
|
30
|
-
const WASM_SDK =
|
|
32
|
+
const WASM_SDK = `${toolchain.release}_wasm`
|
|
31
33
|
const WASM_TRIPLE = 'wasm32-unknown-wasip1'
|
|
32
34
|
|
|
33
35
|
const ARTIFACT_PATH = '/__rnx/swift/'
|
|
@@ -185,6 +187,16 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
185
187
|
enforce: 'pre',
|
|
186
188
|
|
|
187
189
|
async resolveId(source, importer) {
|
|
190
|
+
if (source.startsWith(VIRTUAL_PREFIX)) return source
|
|
191
|
+
// import-analysis re-resolves the served URL form of the id
|
|
192
|
+
// (/@id/__x00__..., with the null byte encoded) while transforming the
|
|
193
|
+
// importer. it still names this module, so answer the decoded id; any
|
|
194
|
+
// other /@id/ url is already resolved and must not fall through to the
|
|
195
|
+
// filesystem resolve below, which would throw for it.
|
|
196
|
+
if (source.startsWith('/@id/')) {
|
|
197
|
+
const decoded = source.slice('/@id/'.length).replace(/^__x00__/, '\0')
|
|
198
|
+
return decoded.startsWith(VIRTUAL_PREFIX) ? decoded : null
|
|
199
|
+
}
|
|
188
200
|
if (!source.endsWith('.swift')) return null
|
|
189
201
|
const resolved = await this.resolve(source, importer, { skipSelf: true })
|
|
190
202
|
if (!resolved) throw new Error(`cannot resolve swift source ${source}`)
|