peachsim 0.1.1635 → 0.1.1640
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 +5 -1
- package/cli/app-config.ts +14 -5
- package/cli/app-stubs.ts +62 -13
- package/cli/bridge-flow-runner.ts +2 -2
- package/cli/cloud-client.ts +60 -40
- package/cli/commands/doctor.ts +13 -5
- package/cli/commands/flow.ts +1 -1
- package/cli/commands/inspect/shared.ts +9 -4
- package/cli/commands/inspect.ts +65 -47
- package/cli/commands/preview.ts +1 -1
- package/cli/commands/record.ts +97 -47
- package/cli/commands/swift.ts +9 -3
- package/cli/commands/upload.ts +48 -42
- package/cli/drivers/playwright-sim-host.ts +65 -42
- package/cli/main.ts +1 -1
- package/cli/outbound-endpoints.ts +7 -5
- package/cli/pause-aware-timeout.ts +4 -0
- package/cli/ws-bridge.ts +19 -10
- package/detox/attempt-events.cjs +62 -5
- package/detox/attempt-events.d.cts +7 -0
- package/detox/expectations.ts +7 -3
- package/detox/index.ts +121 -47
- package/detox/motion-change.cjs +170 -21
- package/detox/motion-change.d.cts +32 -0
- package/detox/navigation-lifecycle.ts +35 -3
- 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 +8 -7
- 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 +4038 -1983
- 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 +509 -86
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/expo-swift-bundle.cjs +3551 -1574
- package/dist-lib/expo-swift-metadata.cjs +9 -7
- package/dist-lib/expo-swift-metadata.mjs +9 -7
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +43633 -2635
- 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 +2748 -128
- 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 +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 +2547 -83
- package/dist-lib/native-app-identity-project.cjs +237 -50
- package/dist-lib/native-app-identity-project.mjs +237 -50
- package/dist-lib/native-app-identity.cjs +29 -30
- package/dist-lib/native-app-identity.mjs +29 -30
- package/dist-lib/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.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/react-native-host-modules.cjs +2 -2
- package/dist-lib/react-native-host-modules.mjs +2 -2
- package/dist-lib/scripts/dev-server-scanner.cjs +8 -6
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +65403 -22739
- package/dist-lib/swift-compile.cjs +93 -32
- package/dist-lib/swift-compile.mjs +93 -32
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +7 -5
- package/dist-lib/swift-submit.mjs +7 -5
- package/dist-lib/swift-toolchain.cjs +316 -41
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +7335 -5432
- package/dist-lib/vite.cjs +2857 -12
- package/dist-lib/worklet-executable.cjs +14 -2
- package/dist-lib/worklet-executable.mjs +14 -2
- package/package.json +1 -1
- package/src/backend-origin.ts +13 -6
- package/src/expo-swift-host.ts +5 -2
- package/src/expoKotlinContract.ts +4 -7
- package/src/expoSwiftContract.ts +4 -6
- package/src/host/staged-host-files.ts +9 -0
- package/src/native-app-identity-project.ts +1 -4
- package/src/native-app-identity.ts +1 -4
- package/src/nativeAppIconContract.ts +1 -4
- package/src/output-lock-stdio.ts +36 -0
- package/src/react-native-bridge-modules.ts +21 -6
- package/src/react-native-host-modules.ts +4 -1
- package/src/settings-bundle.ts +1 -4
- package/src/swift-compile.ts +91 -33
- package/src/swift-distribution.lock.json +22 -12
- package/src/swift-distribution.ts +103 -4
- package/src/swift-host.ts +89 -26
- package/src/swift-icu.ts +16 -10
- package/src/swift-objc-compile.ts +355 -0
- package/src/swift-objc-sdk.ts +689 -18
- package/src/swift-objc-standin.ts +126 -14
- package/src/swift-package-manifest.ts +39 -1
- package/src/swift-packages.ts +35 -21
- package/src/swift-standin-emit.ts +40 -7
- package/src/swift-standins.ts +404 -138
- package/src/swift-submit.ts +3 -5
- package/src/swift-toolchain.ts +378 -46
- package/src/worker-system-chunks.ts +11 -12
- package/src/worklet-executable.ts +19 -1
- package/src/xcode-project.ts +270 -30
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1640 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -185,6 +185,8 @@ function enter(node, scope) {
|
|
|
185
185
|
const name = identifierName(id);
|
|
186
186
|
if (name !== void 0 && isNode(init))
|
|
187
187
|
declare(target, name, { kind: "value", init, scope });
|
|
188
|
+
else if (name !== void 0 && init === null)
|
|
189
|
+
declare(target, name, { kind: "uninitialized" });
|
|
188
190
|
else declareOpaque(target, patternNames(id));
|
|
189
191
|
}
|
|
190
192
|
return scope;
|
|
@@ -254,12 +256,18 @@ function collectInitData(root) {
|
|
|
254
256
|
immediate: /* @__PURE__ */ new Map(),
|
|
255
257
|
writes: [],
|
|
256
258
|
reassigned: /* @__PURE__ */ new Set(),
|
|
259
|
+
assigned: /* @__PURE__ */ new Map(),
|
|
257
260
|
free: /* @__PURE__ */ new Map()
|
|
258
261
|
};
|
|
259
262
|
visit(root, newScope(void 0, true), collection);
|
|
260
263
|
for (const { name, scope, value } of collection.writes) {
|
|
261
264
|
const binding = lookup(scope, name);
|
|
262
|
-
if (binding
|
|
265
|
+
if (binding?.kind === "uninitialized")
|
|
266
|
+
collection.assigned.set(
|
|
267
|
+
binding,
|
|
268
|
+
collection.assigned.has(binding) || !value ? null : { value, scope }
|
|
269
|
+
);
|
|
270
|
+
else if (binding) collection.reassigned.add(binding);
|
|
263
271
|
else
|
|
264
272
|
collection.free.set(
|
|
265
273
|
name,
|
|
@@ -320,6 +328,10 @@ function resolveObject(expression, scope, collection, depth) {
|
|
|
320
328
|
return free ? resolveObject(free.value, free.scope, collection, depth + 1) : void 0;
|
|
321
329
|
}
|
|
322
330
|
if (collection.reassigned.has(binding)) return void 0;
|
|
331
|
+
if (binding.kind === "uninitialized") {
|
|
332
|
+
const assigned = collection.assigned.get(binding);
|
|
333
|
+
return assigned ? resolveObject(assigned.value, assigned.scope, collection, depth + 1) : void 0;
|
|
334
|
+
}
|
|
323
335
|
if (binding.kind === "value")
|
|
324
336
|
return resolveObject(binding.init, binding.scope, collection, depth + 1);
|
|
325
337
|
if (binding.kind === "param") {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1640 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
|
|
3
3
|
// src/worklet-executable.ts
|
|
4
4
|
import { parse } from "acorn";
|
|
@@ -160,6 +160,8 @@ function enter(node, scope) {
|
|
|
160
160
|
const name = identifierName(id);
|
|
161
161
|
if (name !== void 0 && isNode(init))
|
|
162
162
|
declare(target, name, { kind: "value", init, scope });
|
|
163
|
+
else if (name !== void 0 && init === null)
|
|
164
|
+
declare(target, name, { kind: "uninitialized" });
|
|
163
165
|
else declareOpaque(target, patternNames(id));
|
|
164
166
|
}
|
|
165
167
|
return scope;
|
|
@@ -229,12 +231,18 @@ function collectInitData(root) {
|
|
|
229
231
|
immediate: /* @__PURE__ */ new Map(),
|
|
230
232
|
writes: [],
|
|
231
233
|
reassigned: /* @__PURE__ */ new Set(),
|
|
234
|
+
assigned: /* @__PURE__ */ new Map(),
|
|
232
235
|
free: /* @__PURE__ */ new Map()
|
|
233
236
|
};
|
|
234
237
|
visit(root, newScope(void 0, true), collection);
|
|
235
238
|
for (const { name, scope, value } of collection.writes) {
|
|
236
239
|
const binding = lookup(scope, name);
|
|
237
|
-
if (binding
|
|
240
|
+
if (binding?.kind === "uninitialized")
|
|
241
|
+
collection.assigned.set(
|
|
242
|
+
binding,
|
|
243
|
+
collection.assigned.has(binding) || !value ? null : { value, scope }
|
|
244
|
+
);
|
|
245
|
+
else if (binding) collection.reassigned.add(binding);
|
|
238
246
|
else
|
|
239
247
|
collection.free.set(
|
|
240
248
|
name,
|
|
@@ -295,6 +303,10 @@ function resolveObject(expression, scope, collection, depth) {
|
|
|
295
303
|
return free ? resolveObject(free.value, free.scope, collection, depth + 1) : void 0;
|
|
296
304
|
}
|
|
297
305
|
if (collection.reassigned.has(binding)) return void 0;
|
|
306
|
+
if (binding.kind === "uninitialized") {
|
|
307
|
+
const assigned = collection.assigned.get(binding);
|
|
308
|
+
return assigned ? resolveObject(assigned.value, assigned.scope, collection, depth + 1) : void 0;
|
|
309
|
+
}
|
|
298
310
|
if (binding.kind === "value")
|
|
299
311
|
return resolveObject(binding.init, binding.scope, collection, depth + 1);
|
|
300
312
|
if (binding.kind === "param") {
|
package/package.json
CHANGED
package/src/backend-origin.ts
CHANGED
|
@@ -98,12 +98,16 @@ export function isBrowserAssetProxyUrl(value: string): boolean {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// the PeachSim app's host; the backend serves every path on it
|
|
102
|
+
export const PEACHSIM_NATIVE_HOST = 'native.peachsim.com'
|
|
103
|
+
|
|
101
104
|
export function isContrastBackendHost(hostname: string): boolean {
|
|
102
105
|
const normalized = normalizeHostname(hostname)
|
|
103
106
|
return (
|
|
104
107
|
isLoopbackHost(normalized) ||
|
|
105
108
|
normalized === 'contrast.dev' ||
|
|
106
|
-
normalized.endsWith('.contrast.dev')
|
|
109
|
+
normalized.endsWith('.contrast.dev') ||
|
|
110
|
+
normalized === PEACHSIM_NATIVE_HOST
|
|
107
111
|
)
|
|
108
112
|
}
|
|
109
113
|
|
|
@@ -115,15 +119,18 @@ export function isContrastBackendOrigin(origin: string): boolean {
|
|
|
115
119
|
}
|
|
116
120
|
}
|
|
117
121
|
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
// a page's `?backend=` names another backend origin, judged by the same rule
|
|
123
|
+
export function resolveContrastBackendOrigin(
|
|
124
|
+
currentOrigin?: string | null,
|
|
125
|
+
search?: string | null,
|
|
126
|
+
): string {
|
|
127
|
+
const origin =
|
|
128
|
+
new URLSearchParams(search ?? '').get('backend')?.trim() || currentOrigin?.trim()
|
|
120
129
|
if (!origin) return DEFAULT_CONTRAST_BACKEND_ORIGIN
|
|
121
130
|
|
|
122
131
|
try {
|
|
123
132
|
const parsed = new URL(origin)
|
|
124
|
-
if (
|
|
125
|
-
return parsed.origin
|
|
126
|
-
}
|
|
133
|
+
if (isContrastBackendHost(parsed.hostname)) return parsed.origin
|
|
127
134
|
} catch {}
|
|
128
135
|
|
|
129
136
|
return DEFAULT_CONTRAST_BACKEND_ORIGIN
|
package/src/expo-swift-host.ts
CHANGED
|
@@ -136,7 +136,10 @@ export async function collectExpoSwiftSources(
|
|
|
136
136
|
let bytes = 0
|
|
137
137
|
let fileCount = 0
|
|
138
138
|
// native Expo resolves packages by name before generating its Apple provider.
|
|
139
|
-
|
|
139
|
+
// the search result is keyed by that name; older autolinkers add no name field.
|
|
140
|
+
for (const [packageName, entry] of Object.entries(search).sort(([a], [b]) =>
|
|
141
|
+
a.localeCompare(b),
|
|
142
|
+
)) {
|
|
140
143
|
if (!entry || typeof entry !== 'object')
|
|
141
144
|
throw new Error('invalid Expo autolinking entry')
|
|
142
145
|
const sourceRoot = Reflect.get(entry, 'path')
|
|
@@ -149,7 +152,7 @@ export async function collectExpoSwiftSources(
|
|
|
149
152
|
(candidate) =>
|
|
150
153
|
candidate &&
|
|
151
154
|
typeof candidate === 'object' &&
|
|
152
|
-
Reflect.get(candidate, 'packageName') ===
|
|
155
|
+
Reflect.get(candidate, 'packageName') === packageName,
|
|
153
156
|
)
|
|
154
157
|
const namespaces =
|
|
155
158
|
resolution && typeof resolution === 'object'
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { insertBundleGlobalFooter, readBundleGlobalFooter } from './bundleGlobalFooter'
|
|
2
|
+
import { isRecord } from './is-record'
|
|
2
3
|
|
|
3
4
|
// what the dev server answers for an app's local Expo Kotlin modules on an
|
|
4
5
|
// Android device: one linked Kotlin/Wasm process, or each module's diagnostic.
|
|
@@ -10,14 +11,10 @@ export interface ExpoKotlinCompilationResult {
|
|
|
10
11
|
failures: { namespace: string; diagnostic: string }[]
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
function record(value: unknown): value is Record<string, unknown> {
|
|
14
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
14
|
export function readExpoKotlinMetadata(value: unknown): ExpoKotlinCompilationResult {
|
|
18
15
|
const invalid = () => new Error('invalid Expo Kotlin compilation metadata')
|
|
19
16
|
if (
|
|
20
|
-
!
|
|
17
|
+
!isRecord(value) ||
|
|
21
18
|
!Array.isArray(value.namespaces) ||
|
|
22
19
|
!Array.isArray(value.failures)
|
|
23
20
|
)
|
|
@@ -30,7 +27,7 @@ export function readExpoKotlinMetadata(value: unknown): ExpoKotlinCompilationRes
|
|
|
30
27
|
const failures: ExpoKotlinCompilationResult['failures'] = []
|
|
31
28
|
for (const failure of value.failures) {
|
|
32
29
|
if (
|
|
33
|
-
!
|
|
30
|
+
!isRecord(failure) ||
|
|
34
31
|
typeof failure.namespace !== 'string' ||
|
|
35
32
|
typeof failure.diagnostic !== 'string'
|
|
36
33
|
)
|
|
@@ -40,7 +37,7 @@ export function readExpoKotlinMetadata(value: unknown): ExpoKotlinCompilationRes
|
|
|
40
37
|
const module = value.module
|
|
41
38
|
if (module === null) return { namespaces, module: null, failures }
|
|
42
39
|
if (
|
|
43
|
-
!
|
|
40
|
+
!isRecord(module) ||
|
|
44
41
|
typeof module.url !== 'string' ||
|
|
45
42
|
typeof module.hash !== 'string' ||
|
|
46
43
|
typeof module.compose !== 'boolean'
|
package/src/expoSwiftContract.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { insertBundleGlobalFooter, readBundleGlobalFooter } from './bundleGlobalFooter'
|
|
2
|
+
import { isRecord } from './is-record'
|
|
2
3
|
|
|
3
4
|
export interface ExpoSwiftPreparationOptions {
|
|
4
5
|
signal?: AbortSignal
|
|
@@ -33,9 +34,6 @@ export interface ExpoSwiftCompilationResult {
|
|
|
33
34
|
failures: { namespace: string; diagnostic: string }[]
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
function record(value: unknown): value is Record<string, unknown> {
|
|
37
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
38
|
-
}
|
|
39
37
|
function strings(value: unknown): value is string[] {
|
|
40
38
|
return Array.isArray(value) && value.every((item) => typeof item === 'string')
|
|
41
39
|
}
|
|
@@ -44,7 +42,7 @@ export function isExpoSwiftCompilationResult(
|
|
|
44
42
|
value: unknown,
|
|
45
43
|
): value is ExpoSwiftCompilationResult {
|
|
46
44
|
if (
|
|
47
|
-
!
|
|
45
|
+
!isRecord(value) ||
|
|
48
46
|
!strings(value.namespaces) ||
|
|
49
47
|
!Array.isArray(value.modules) ||
|
|
50
48
|
value.modules.length > 1 ||
|
|
@@ -62,7 +60,7 @@ export function isExpoSwiftCompilationResult(
|
|
|
62
60
|
return false
|
|
63
61
|
for (const artifact of value.modules) {
|
|
64
62
|
if (
|
|
65
|
-
!
|
|
63
|
+
!isRecord(artifact) ||
|
|
66
64
|
!strings(artifact.namespaces) ||
|
|
67
65
|
artifact.namespaces.length === 0 ||
|
|
68
66
|
!artifact.namespaces.every((namespace) => remaining.delete(namespace)) ||
|
|
@@ -75,7 +73,7 @@ export function isExpoSwiftCompilationResult(
|
|
|
75
73
|
}
|
|
76
74
|
for (const failure of value.failures) {
|
|
77
75
|
if (
|
|
78
|
-
!
|
|
76
|
+
!isRecord(failure) ||
|
|
79
77
|
typeof failure.namespace !== 'string' ||
|
|
80
78
|
!remaining.delete(failure.namespace) ||
|
|
81
79
|
typeof failure.diagnostic !== 'string'
|
|
@@ -95,6 +95,15 @@ export async function handleStagedHostFileRequest(
|
|
|
95
95
|
res.end('staged file not found')
|
|
96
96
|
return
|
|
97
97
|
}
|
|
98
|
+
if (req.method === 'OPTIONS') {
|
|
99
|
+
res.writeHead(204, {
|
|
100
|
+
'Access-Control-Allow-Methods': 'GET, HEAD',
|
|
101
|
+
'Access-Control-Allow-Headers':
|
|
102
|
+
req.headers['access-control-request-headers'] ?? '*',
|
|
103
|
+
})
|
|
104
|
+
res.end()
|
|
105
|
+
return
|
|
106
|
+
}
|
|
98
107
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
99
108
|
res.writeHead(405, { Allow: 'GET, HEAD' })
|
|
100
109
|
res.end('method not allowed')
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs'
|
|
|
2
2
|
import { createRequire } from 'node:module'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { androidMergedResourceValuesDirectory } from './android-app-identity'
|
|
5
|
+
import { isRecord } from './is-record'
|
|
5
6
|
import {
|
|
6
7
|
extractNativeAppIdentityFromFiles,
|
|
7
8
|
type NativeAppIdentity,
|
|
@@ -17,10 +18,6 @@ import {
|
|
|
17
18
|
type XcodeAppTarget,
|
|
18
19
|
} from './xcode-project'
|
|
19
20
|
|
|
20
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
21
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
21
|
function nonEmptyString(value: unknown): string | undefined {
|
|
25
22
|
return typeof value === 'string' && value.length > 0 ? value : undefined
|
|
26
23
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { nativeAppIdentityFromAndroidManifest } from './android-app-identity'
|
|
12
12
|
import { findBundleFooter } from './bundleGlobalFooter'
|
|
13
|
+
import { isRecord } from './is-record'
|
|
13
14
|
import {
|
|
14
15
|
resolveNativeAppIconArtwork,
|
|
15
16
|
isNativeAppIconArtwork,
|
|
@@ -99,10 +100,6 @@ interface NativeAppIdentityGlobals {
|
|
|
99
100
|
__sootsim_native_app_identity?: NativeAppIdentity | null
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
103
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
103
|
function nonEmptyString(value: unknown): string | null {
|
|
107
104
|
return typeof value === 'string' && value.length > 0 ? value : null
|
|
108
105
|
}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { isSwiftImageVariants, type SwiftImageVariants } from './assetCatalogContract'
|
|
2
|
+
import { isRecord } from './is-record'
|
|
2
3
|
|
|
3
4
|
export interface NativeAppIconArtwork {
|
|
4
5
|
primary?: SwiftImageVariants
|
|
5
6
|
alternates: Record<string, SwiftImageVariants>
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
9
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
9
|
export function isNativeAppIconArtwork(value: unknown): value is NativeAppIconArtwork {
|
|
13
10
|
return (
|
|
14
11
|
isRecord(value) &&
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { fstatSync } from 'node:fs'
|
|
2
|
+
import type { IOType, StdioOptions } from 'node:child_process'
|
|
3
|
+
|
|
4
|
+
// lock identities name actual inherited descriptors, never paths to reopen.
|
|
5
|
+
export function outputLockStdio(
|
|
6
|
+
standard: [IOType, IOType, IOType],
|
|
7
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
8
|
+
): StdioOptions {
|
|
9
|
+
const stdio: StdioOptions = [...standard]
|
|
10
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
11
|
+
if (key.startsWith('REMOTE_OUTPUT_LOCK_') && env[key] !== value)
|
|
12
|
+
throw new Error(`inherited output lock identity changed for ${key}`)
|
|
13
|
+
}
|
|
14
|
+
for (const [key, value] of Object.entries(env)) {
|
|
15
|
+
if (!key.startsWith('REMOTE_OUTPUT_LOCK_')) continue
|
|
16
|
+
const fd = Number(value)
|
|
17
|
+
if (
|
|
18
|
+
!/^REMOTE_OUTPUT_LOCK_[a-f0-9]{64}$/.test(key) ||
|
|
19
|
+
!value ||
|
|
20
|
+
!/^\d+$/.test(value) ||
|
|
21
|
+
!Number.isSafeInteger(fd) ||
|
|
22
|
+
fd < 3
|
|
23
|
+
)
|
|
24
|
+
throw new Error(`invalid inherited output lock descriptor for ${key}`)
|
|
25
|
+
try {
|
|
26
|
+
fstatSync(fd)
|
|
27
|
+
} catch (cause) {
|
|
28
|
+
throw new Error(`inherited output lock descriptor ${fd} unavailable for ${key}`, {
|
|
29
|
+
cause,
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
while (stdio.length <= fd) stdio.push('ignore')
|
|
33
|
+
stdio[fd] = fd
|
|
34
|
+
}
|
|
35
|
+
return stdio
|
|
36
|
+
}
|
|
@@ -326,6 +326,7 @@ export function collectBridgeModuleSources(
|
|
|
326
326
|
signal?.throwIfAborted()
|
|
327
327
|
const iosRoot = path.join(appRoot, 'ios')
|
|
328
328
|
if (!fs.existsSync(iosRoot)) return null
|
|
329
|
+
const projectRoot = fs.realpathSync(appRoot)
|
|
329
330
|
const projects = fs
|
|
330
331
|
.readdirSync(iosRoot, { withFileTypes: true })
|
|
331
332
|
.filter((entry) => entry.isDirectory() && entry.name.endsWith('.xcodeproj'))
|
|
@@ -342,8 +343,23 @@ export function collectBridgeModuleSources(
|
|
|
342
343
|
const app = readXcodeAppTarget(targets[0]!.project, targets[0]!.name, 'Release', {
|
|
343
344
|
requireSources: false,
|
|
344
345
|
})
|
|
345
|
-
const own = (file: string) =>
|
|
346
|
-
|
|
346
|
+
const own = (file: string) => {
|
|
347
|
+
const resolved = fs.realpathSync(file)
|
|
348
|
+
if (
|
|
349
|
+
[file, resolved].some((name) =>
|
|
350
|
+
name.split(path.sep).some((part) => part === 'Pods' || part === 'node_modules'),
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
return false
|
|
354
|
+
const relative = path.relative(projectRoot, resolved)
|
|
355
|
+
if (
|
|
356
|
+
relative === '..' ||
|
|
357
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
358
|
+
path.isAbsolute(relative)
|
|
359
|
+
)
|
|
360
|
+
throw new Error(`${file}: app native source outside ${projectRoot}`)
|
|
361
|
+
return true
|
|
362
|
+
}
|
|
347
363
|
const objc = app.allSources.filter((file) => /\.mm?$/.test(file) && own(file))
|
|
348
364
|
if (objc.length === 0) return null
|
|
349
365
|
// headers declare the superclasses; they sit beside the sources that import them
|
|
@@ -352,7 +368,8 @@ export function collectBridgeModuleSources(
|
|
|
352
368
|
.readdirSync(dir)
|
|
353
369
|
.filter((name) => name.endsWith('.h'))
|
|
354
370
|
.sort()
|
|
355
|
-
.map((name) => path.join(dir, name))
|
|
371
|
+
.map((name) => path.join(dir, name))
|
|
372
|
+
.filter(own),
|
|
356
373
|
)
|
|
357
374
|
const declarations = parseObjectiveCModules(
|
|
358
375
|
[...objc, ...headers].map((file) => ({ file, text: fs.readFileSync(file, 'utf8') })),
|
|
@@ -361,9 +378,7 @@ export function collectBridgeModuleSources(
|
|
|
361
378
|
const namespace = app.moduleName.replace(/[^A-Za-z0-9_]/g, '_')
|
|
362
379
|
const swiftFiles: Record<string, string> = {}
|
|
363
380
|
for (const file of app.sources.filter(own)) {
|
|
364
|
-
const relative = path.relative(
|
|
365
|
-
if (relative.startsWith('..') || path.isAbsolute(relative))
|
|
366
|
-
throw new Error(`${file}: app Swift source outside ${iosRoot}`)
|
|
381
|
+
const relative = path.relative(projectRoot, fs.realpathSync(file))
|
|
367
382
|
swiftFiles[`Sources/${namespace}/${relative.split(path.sep).join('/')}`] =
|
|
368
383
|
`#sourceLocation(file: ${JSON.stringify(file)}, line: 1)\n${fs.readFileSync(file, 'utf8')}`
|
|
369
384
|
}
|
|
@@ -43,7 +43,10 @@ export const REACT_NATIVE_PASSTHROUGH_SPECIFIERS: readonly string[] = [
|
|
|
43
43
|
]
|
|
44
44
|
|
|
45
45
|
export function isReactNativePassthroughSpecifier(specifier: string): boolean {
|
|
46
|
+
// checked in place: every module of a bundle asks at boot
|
|
46
47
|
return REACT_NATIVE_PASSTHROUGH_SPECIFIERS.some(
|
|
47
|
-
(passthrough) =>
|
|
48
|
+
(passthrough) =>
|
|
49
|
+
specifier.startsWith(passthrough) &&
|
|
50
|
+
(specifier.length === passthrough.length || specifier[passthrough.length] === '/'),
|
|
48
51
|
)
|
|
49
52
|
}
|
package/src/settings-bundle.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile, readdir, stat } from 'node:fs/promises'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
|
+
import { isRecord } from './is-record'
|
|
3
4
|
import { parsePlistSource } from './plist.ts'
|
|
4
5
|
import type { AppPreferenceValue, AppSettingsSpecifier } from '@peach/globals'
|
|
5
6
|
|
|
@@ -22,10 +23,6 @@ interface CachedSettingsBundle {
|
|
|
22
23
|
|
|
23
24
|
const settingsBundleCache = new Map<string, CachedSettingsBundle>()
|
|
24
25
|
|
|
25
|
-
function isRecord(value: unknown): value is PlistRecord {
|
|
26
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
26
|
function optionalString(value: unknown): string | undefined {
|
|
30
27
|
return typeof value === 'string' ? value : undefined
|
|
31
28
|
}
|