catalyst-core 1.1.0 → 1.2.0-canary.2

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.
Files changed (69) hide show
  1. package/bin/catalyst.js +54 -6
  2. package/changelog.md +27 -1
  3. package/dist/errors/registry.js +306 -18
  4. package/dist/errors-index.json +273 -70
  5. package/dist/native/androidProject/app/build.gradle.kts +53 -79
  6. package/dist/native/androidProject/app/src/main/AndroidManifest.xml +1 -1
  7. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CatalystConstants.kt +3 -1
  8. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +32 -4
  9. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/KeyboardUtil.kt +5 -1
  10. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/ConfigMappingTest.kt +25 -0
  11. package/dist/native/androidSetup.js +76 -170
  12. package/dist/native/androidToolchain.js +642 -0
  13. package/dist/native/appIcon.js +100 -0
  14. package/dist/native/buildAndroid/assets.js +29 -5
  15. package/dist/native/buildAndroid/build.js +110 -223
  16. package/dist/native/buildAndroid/config.js +57 -65
  17. package/dist/native/buildAndroid/devices.js +176 -0
  18. package/dist/native/buildAndroid/index.js +271 -46
  19. package/dist/native/buildAndroid/output.js +43 -0
  20. package/dist/native/buildAndroid/signing.js +456 -0
  21. package/dist/native/buildAppAndroid.js +27 -7
  22. package/dist/native/buildAppIos.js +139 -20
  23. package/dist/native/buildIos/assets.js +76 -4
  24. package/dist/native/buildIos/build.js +31 -181
  25. package/dist/native/buildIos/config.js +126 -11
  26. package/dist/native/buildIos/index.js +354 -34
  27. package/dist/native/buildIos/localhostCert.js +87 -0
  28. package/dist/native/buildIos/output.js +28 -0
  29. package/dist/native/buildIos/signing.js +596 -0
  30. package/dist/native/credentials.js +309 -0
  31. package/dist/native/exportCredentials.js +51 -0
  32. package/dist/native/iosToolchain.js +583 -0
  33. package/dist/native/iosnativeWebView/Configurations/Shared.xcconfig +1 -3
  34. package/dist/native/iosnativeWebView/iosnativeWebView/Info-Release.plist +2 -0
  35. package/dist/native/iosnativeWebView/iosnativeWebView/PrivacyInfo.xcprivacy +40 -0
  36. package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebView.entitlements +0 -2
  37. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +4 -4
  38. package/dist/native/localState.js +148 -0
  39. package/dist/native/nativeConfig.js +621 -0
  40. package/dist/native/releaseOutput.js +53 -0
  41. package/dist/native/renameAndroidProject.js +154 -369
  42. package/dist/native/setupEmulatorIos.js +58 -275
  43. package/dist/native/setupServer.js +520 -124
  44. package/dist/native/utils.js +418 -115
  45. package/dist/vite/resolveDevServerConfig.js +21 -0
  46. package/mcp_v2/knowledge-base.json +13 -11
  47. package/mcp_v2/test/config.test.ts +40 -0
  48. package/mcp_v2/tools/build.js +80 -104
  49. package/mcp_v2/tools/config.js +181 -174
  50. package/package.json +1 -1
  51. package/test/cli/nativeFlags.test.ts +97 -0
  52. package/test/native/androidDevices.test.ts +230 -0
  53. package/test/native/androidElfAlignment.test.ts +148 -0
  54. package/test/native/androidSigning.test.ts +646 -0
  55. package/test/native/androidToolchain.test.ts +766 -0
  56. package/test/native/appIcon.test.ts +124 -0
  57. package/test/native/credentials.test.ts +241 -0
  58. package/test/native/interaction.test.ts +179 -0
  59. package/test/native/iosConfigPhase.test.ts +611 -0
  60. package/test/native/iosSigning.test.ts +1060 -0
  61. package/test/native/iosToolchain.test.ts +821 -0
  62. package/test/native/localState.test.ts +80 -0
  63. package/test/native/nativeConfig.test.ts +602 -0
  64. package/test/native/setupServer.test.ts +529 -0
  65. package/test/native/utils.test.ts +102 -127
  66. package/test/vite/resolveDevServerConfig.test.js +25 -0
  67. package/dist/native/androidProject/keystore/Untitled +0 -0
  68. package/dist/native/androidProject/keystore/release-key.jks +0 -0
  69. package/dist/native/iosnativeWebView/iosnativeWebView/localhost.p12 +0 -0
@@ -0,0 +1,583 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DEFAULT_SIMULATOR_NAME = void 0;
7
+ exports.assertFullXcode = assertFullXcode;
8
+ exports.availableIosRuntimes = availableIosRuntimes;
9
+ exports.chooseIosTarget = chooseIosTarget;
10
+ exports.devHostFor = devHostFor;
11
+ exports.ensureIosRuntime = ensureIosRuntime;
12
+ exports.ensureSimulator = ensureSimulator;
13
+ exports.ensureXcodeReady = ensureXcodeReady;
14
+ exports.findSimulator = findSimulator;
15
+ exports.listConnectedIphones = listConnectedIphones;
16
+ exports.newestIphoneDeviceType = newestIphoneDeviceType;
17
+ exports.newestIphoneSimulator = newestIphoneSimulator;
18
+ exports.parseDevicectlDevices = parseDevicectlDevices;
19
+ exports.pendingXcodeSetup = pendingXcodeSetup;
20
+ exports.prepareIosDebugTarget = prepareIosDebugTarget;
21
+ exports.probe = probe;
22
+ exports.runDevicectlList = runDevicectlList;
23
+ exports.simctlJson = simctlJson;
24
+ exports.simulatorsFor = simulatorsFor;
25
+ var _child_process = require("child_process");
26
+ var _fs = _interopRequireDefault(require("fs"));
27
+ var _os = _interopRequireDefault(require("os"));
28
+ var _path = _interopRequireDefault(require("path"));
29
+ var _utils = require("./utils.js");
30
+ var _setupServer = require("./setupServer.js");
31
+ var _localState = require("./localState.js");
32
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
33
+ // The iOS toolchain checks `buildApp:ios` runs before it builds, and
34
+ // `setupEmulator:ios` runs on its own: full Xcode with its licence and
35
+ // first-launch components, an iOS simulator runtime, a simulator, and the
36
+ // choice between that simulator and a connected iPhone. Installs ask first
37
+ // (askConsent "install", answered by --yes), the licence asks separately
38
+ // ("licence", answered by --accept-licenses). Nothing here writes config.json.
39
+
40
+ const DEFAULT_SIMULATOR_NAME = exports.DEFAULT_SIMULATOR_NAME = "Catalyst iPhone";
41
+
42
+ /** Exit status and output of a probe; never throws. */
43
+ function probe(command, args, {
44
+ spawn = _child_process.spawnSync
45
+ } = {}) {
46
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - Fixed Xcode probe commands with argv arrays.
47
+ const result = spawn(command, args, {
48
+ encoding: "utf8",
49
+ timeout: 60000
50
+ });
51
+ return {
52
+ status: result.error ? null : result.status,
53
+ output: `${result.stdout || ""}${result.stderr || ""}`.trim()
54
+ };
55
+ }
56
+ function simctlJson(args, {
57
+ exec = _child_process.execFileSync
58
+ } = {}) {
59
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - Fixed simctl queries with argv arrays.
60
+ return JSON.parse(exec("xcrun", ["simctl", ...args, "-j"], {
61
+ encoding: "utf8"
62
+ }));
63
+ }
64
+
65
+ /**
66
+ * The active developer directory must be inside an Xcode app bundle. The
67
+ * Command Line Tools alone have no simulator, SDKs or xcodebuild that builds
68
+ * apps. Throws IOS-002 otherwise.
69
+ */
70
+ function assertFullXcode({
71
+ run = probe
72
+ } = {}) {
73
+ const {
74
+ status,
75
+ output
76
+ } = run("xcode-select", ["-p"]);
77
+ const dir = status === 0 ? output.split("\n")[0].trim() : "";
78
+ if (!/\.app\/Contents\/Developer\/?$/.test(dir)) {
79
+ throw (0, _utils.nativeError)("IOS-002", dir ? `xcode-select points at ${dir}` : "xcode-select has no developer directory");
80
+ }
81
+ return dir;
82
+ }
83
+ const XCODE_SETUP_STEPS = [{
84
+ label: "the Xcode licence",
85
+ consent: "licence",
86
+ flag: "--accept-licenses",
87
+ question: "Accept the Xcode licence (runs sudo xcodebuild -license accept)",
88
+ check: ["xcodebuild", "-license", "check"],
89
+ fix: ["sudo", "xcodebuild", "-license", "accept"]
90
+ }, {
91
+ label: "Xcode's first-launch components",
92
+ consent: "install",
93
+ flag: "--yes",
94
+ question: "Install Xcode's first-launch components (runs sudo xcodebuild -runFirstLaunch)",
95
+ check: ["xcodebuild", "-checkFirstLaunchStatus"],
96
+ fix: ["sudo", "xcodebuild", "-runFirstLaunch"]
97
+ }];
98
+
99
+ /** The setup steps Xcode still needs, each with the sudo command that does it. */
100
+ function pendingXcodeSetup({
101
+ run = probe
102
+ } = {}) {
103
+ return XCODE_SETUP_STEPS.filter(step => run(step.check[0], step.check.slice(1)).status !== 0);
104
+ }
105
+
106
+ /**
107
+ * Full Xcode, licence accepted and first launch done.
108
+ *
109
+ * Each pending step is printed with its sudo command, then asked for: the
110
+ * licence with askConsent("licence") (only --accept-licenses answers it
111
+ * without a terminal), the first-launch install with askConsent("install")
112
+ * (--yes). In a terminal sudo asks for the password there. Without one the
113
+ * command runs as `sudo -n`, which works only where sudo needs no password
114
+ * (hosted CI runners); otherwise the error names the command to run.
115
+ *
116
+ * @param {Record<string, any>} [options] - `flags` ({ yes, acceptLicenses } from parseNativeArgs) and `log`; the rest are seams for tests
117
+ */
118
+ async function ensureXcodeReady({
119
+ flags = {},
120
+ run = probe,
121
+ isTTY = (0, _utils.isInteractive)(),
122
+ ask = _utils.askConsent,
123
+ runInherited = _utils.runInheritedCommand,
124
+ log = () => {}
125
+ } = {}) {
126
+ const developerDir = assertFullXcode({
127
+ run
128
+ });
129
+ const pending = pendingXcodeSetup({
130
+ run
131
+ });
132
+ if (!pending.length) return developerDir;
133
+ for (const step of pending) {
134
+ const command = step.fix.join(" ");
135
+ log(`Xcode needs ${step.label}. To do it yourself, run: ${command}`, "note");
136
+ const answered = step.consent === "licence" ? flags.acceptLicenses : flags.yes;
137
+ if (!isTTY && !answered) {
138
+ throw (0, _utils.needsAnswerError)(step.question, {
139
+ flag: step.flag,
140
+ hint: `Run ${command} yourself, or pass ${step.flag} where sudo needs no password (CI), or run the build in a terminal.`
141
+ });
142
+ }
143
+ if (!(await ask(step.consent, step.question, {
144
+ flags,
145
+ isTTY
146
+ }))) {
147
+ throw (0, _utils.nativeError)("IOS-003", command);
148
+ }
149
+ const argv = isTTY ? step.fix : ["sudo", "-n", ...step.fix.slice(1)];
150
+ try {
151
+ await runInherited(argv[0], argv.slice(1));
152
+ } catch (error) {
153
+ throw (0, _utils.nativeError)("IOS-003", `${command} did not finish (${error.message})`);
154
+ }
155
+ }
156
+ const still = pendingXcodeSetup({
157
+ run
158
+ });
159
+ if (still.length) throw (0, _utils.nativeError)("IOS-003", still.map(step => step.fix.join(" ")).join(" && "));
160
+ return developerDir;
161
+ }
162
+ function compareVersions(a, b) {
163
+ const pa = String(a).split(".").map(Number);
164
+ const pb = String(b).split(".").map(Number);
165
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
166
+ const diff = (pa[i] || 0) - (pb[i] || 0);
167
+ if (diff) return diff;
168
+ }
169
+ return 0;
170
+ }
171
+ const isAvailable = item => item.isAvailable === true || item.availability === "(available)" || item.availability === "available";
172
+
173
+ /** Available iOS simulator runtimes, newest first. */
174
+ function availableIosRuntimes(runtimesJson) {
175
+ return (runtimesJson?.runtimes || []).filter(runtime => (runtime.platform === "iOS" || /SimRuntime\.iOS-/.test(runtime.identifier)) && isAvailable(runtime)).sort((a, b) => compareVersions(b.version, a.version));
176
+ }
177
+
178
+ /**
179
+ * The newest available iOS runtime. With none, `xcodebuild -downloadPlatform
180
+ * iOS` (several GB) runs after askConsent("install") (a terminal yes or
181
+ * --yes). Without a terminal and without --yes it fails with NATIVE-005; a
182
+ * no fails with IOS-004.
183
+ */
184
+ async function ensureIosRuntime({
185
+ flags = {},
186
+ isTTY = (0, _utils.isInteractive)(),
187
+ ask = _utils.askConsent,
188
+ runInherited = _utils.runInheritedCommand,
189
+ listRuntimes = () => simctlJson(["list", "runtimes"]),
190
+ log = () => {}
191
+ } = {}) {
192
+ let [runtime] = availableIosRuntimes(listRuntimes());
193
+ if (runtime) return runtime;
194
+ const consent = await ask("install", "No iOS simulator runtime is installed. Download the latest (several GB)", {
195
+ flags,
196
+ isTTY
197
+ });
198
+ if (!consent) throw (0, _utils.nativeError)("IOS-004", "the download was declined");
199
+ log("Downloading the iOS simulator runtime", "note");
200
+ await runInherited("xcodebuild", ["-downloadPlatform", "iOS"]);
201
+ [runtime] = availableIosRuntimes(listRuntimes());
202
+ if (!runtime) throw (0, _utils.nativeError)("IOS-004", "xcodebuild -downloadPlatform iOS finished without an available runtime");
203
+ return runtime;
204
+ }
205
+
206
+ /**
207
+ * The newest iPhone this runtime supports. simctl lists a runtime's supported
208
+ * device types newest first; the global list is the fallback, ordered by the
209
+ * oldest runtime each type needs.
210
+ */
211
+ function newestIphoneDeviceType(runtime, deviceTypesJson) {
212
+ const isIphone = type => type.productFamily === "iPhone" || /^iPhone/.test(type.name || "");
213
+ const supported = (runtime?.supportedDeviceTypes || []).filter(isIphone);
214
+ if (supported.length) return supported[0];
215
+ const all = (deviceTypesJson?.devicetypes || []).filter(isIphone);
216
+ return all.sort((a, b) => (b.minRuntimeVersion || 0) - (a.minRuntimeVersion || 0))[0] || null;
217
+ }
218
+
219
+ /** Available simulators under a runtime. */
220
+ function simulatorsFor(runtime, devicesJson) {
221
+ return (devicesJson?.devices?.[runtime.identifier] || []).filter(isAvailable);
222
+ }
223
+
224
+ /**
225
+ * The available simulator called `name`: under `runtime` first, then under
226
+ * any other runtime. Null when there is none.
227
+ */
228
+ function findSimulator(name, runtime, devicesJson) {
229
+ const preferred = runtime ? simulatorsFor(runtime, devicesJson).find(sim => sim.name === name) : null;
230
+ if (preferred) return preferred;
231
+ for (const devices of Object.values(devicesJson?.devices || {})) {
232
+ const match = (devices || []).find(sim => sim.name === name && isAvailable(sim));
233
+ if (match) return match;
234
+ }
235
+ return null;
236
+ }
237
+
238
+ /** An iPhone simulator, by device type, else by name (test fixtures carry no type). */
239
+ const isIphoneSimulator = sim => /\.iPhone-/.test(sim?.deviceTypeIdentifier || "") || /^iPhone\b/.test(sim?.name || "");
240
+
241
+ /** "26.0" from "com.apple.CoreSimulator.SimRuntime.iOS-26-0"; null for other platforms. */
242
+ const iosRuntimeVersion = identifier => {
243
+ const match = /SimRuntime\.iOS-([\d-]+)$/.exec(identifier);
244
+ return match ? match[1].replace(/-/g, ".") : null;
245
+ };
246
+
247
+ /**
248
+ * Available iPhone simulators, grouped by iOS runtime: `runtime` first, then
249
+ * the other runtimes newest first.
250
+ *
251
+ * @returns {{ runtimeIdentifier: string, simulators: Record<string, any>[] }[]}
252
+ */
253
+ function iphoneSimulatorsByRuntime(runtime, devicesJson) {
254
+ return Object.entries(devicesJson?.devices || {}).filter(([identifier]) => identifier === runtime?.identifier || iosRuntimeVersion(identifier)).sort(([a], [b]) => {
255
+ if (a === runtime?.identifier) return -1;
256
+ if (b === runtime?.identifier) return 1;
257
+ return compareVersions(iosRuntimeVersion(b), iosRuntimeVersion(a));
258
+ }).map(([runtimeIdentifier, sims]) => ({
259
+ runtimeIdentifier,
260
+ simulators: (sims || []).filter(sim => isAvailable(sim) && isIphoneSimulator(sim))
261
+ })).filter(group => group.simulators.length);
262
+ }
263
+
264
+ /**
265
+ * The newest of these iPhone simulators, preferring a Pro that is not a Max.
266
+ * Newest is the runtime's supportedDeviceTypes order (simctl lists it newest
267
+ * first), then the model number in the name; name and udid break ties so the
268
+ * pick is the same on every run.
269
+ */
270
+ function newestIphoneSimulator(simulators, runtime) {
271
+ const order = (runtime?.supportedDeviceTypes || []).map(type => type.identifier);
272
+ const rank = sim => {
273
+ const index = order.indexOf(sim.deviceTypeIdentifier);
274
+ return index === -1 ? order.length : index;
275
+ };
276
+ const model = sim => Number((/^iPhone (\d+)/.exec(sim.name || "") || [])[1] || 0);
277
+ const sorted = [...simulators].sort((a, b) => rank(a) - rank(b) || model(b) - model(a) || String(a.name).localeCompare(String(b.name)) || String(a.udid).localeCompare(String(b.udid)));
278
+ const pro = sorted.find(sim => /\bPro\b/.test(sim.name || "") && !/\bMax\b/.test(sim.name || ""));
279
+ return pro || sorted[0] || null;
280
+ }
281
+
282
+ /**
283
+ * The simulator to build for. Install only what is missing, never ask for
284
+ * what can be derived:
285
+ *
286
+ * 1. `name` (ios.simulatorName) when it exists on an available runtime;
287
+ * 2. the remembered pick (`last`, getLastTarget("ios")) while its udid is
288
+ * still available;
289
+ * 3. an iPhone simulator that is already booted;
290
+ * 4. the newest iPhone simulator on `runtime` (a Pro that is not a Max
291
+ * first), else on the newest other runtime that has one;
292
+ * 5. only when there is no iPhone simulator at all, "Catalyst iPhone" is
293
+ * created for the newest iPhone the runtime supports, after
294
+ * askConsent("install").
295
+ *
296
+ * @param {Record<string, any>} [options] - `runtime` (from ensureIosRuntime), `name` (ios.simulatorName), `last`, `flags`, `log`; the rest are seams for tests
297
+ * @returns {Promise<{ name: string, udid: string, created: boolean }>}
298
+ */
299
+ async function ensureSimulator({
300
+ runtime,
301
+ name,
302
+ last = null,
303
+ flags = {},
304
+ isTTY = (0, _utils.isInteractive)(),
305
+ ask = _utils.askConsent,
306
+ devicesJson = simctlJson(["list", "devices"]),
307
+ deviceTypesJson,
308
+ create = (simName, deviceType, rt) =>
309
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - simctl with an argv array; the name is passed as one argument.
310
+ (0, _child_process.execFileSync)("xcrun", ["simctl", "create", simName, deviceType.identifier, rt.identifier], {
311
+ encoding: "utf8"
312
+ }),
313
+ log = () => {}
314
+ } = {}) {
315
+ const use = sim => ({
316
+ name: sim.name,
317
+ udid: sim.udid,
318
+ created: false
319
+ });
320
+ if (name) {
321
+ const configured = findSimulator(name, runtime, devicesJson);
322
+ if (configured) return use(configured);
323
+ log(`ios.simulatorName "${name}" is not an available simulator; picking one`, "warning");
324
+ }
325
+ const allAvailable = Object.values(devicesJson?.devices || {}).flat().filter(sim => sim && isAvailable(sim));
326
+ if (last?.target === "simulator" && last.udid) {
327
+ const remembered = allAvailable.find(sim => sim.udid === last.udid);
328
+ if (remembered) return use(remembered);
329
+ }
330
+ const groups = iphoneSimulatorsByRuntime(runtime, devicesJson);
331
+ const booted = groups.flatMap(group => group.simulators).find(sim => sim.state === "Booted");
332
+ if (booted) return use(booted);
333
+ if (groups.length) {
334
+ const [group] = groups;
335
+ const groupRuntime = group.runtimeIdentifier === runtime?.identifier ? runtime : null;
336
+ return use(newestIphoneSimulator(group.simulators, groupRuntime));
337
+ }
338
+ const deviceType = newestIphoneDeviceType(runtime, deviceTypesJson || simctlJson(["list", "devicetypes"]));
339
+ if (!deviceType) throw new Error(`No iPhone device type is available for ${runtime.name}`);
340
+ const consent = await ask("install", `No iPhone simulator is available. Create the "${DEFAULT_SIMULATOR_NAME}" simulator (${deviceType.name || "iPhone"}, ${runtime.name})`, {
341
+ flags,
342
+ isTTY
343
+ });
344
+ if (!consent) {
345
+ throw new Error("No iPhone simulator is available; create one in Xcode (Window > Devices and Simulators)");
346
+ }
347
+ const udid = String(create(DEFAULT_SIMULATOR_NAME, deviceType, runtime) || "").trim();
348
+ log(`Created simulator "${DEFAULT_SIMULATOR_NAME}"`, "success");
349
+ return {
350
+ name: DEFAULT_SIMULATOR_NAME,
351
+ udid,
352
+ created: true
353
+ };
354
+ }
355
+
356
+ // Apple's identifiers are hex digits and hyphens (a UUID, or the 40-char
357
+ // legacy UDID). The udid ends up in xcodebuild and devicectl arguments, so
358
+ // anything else from devicectl's output is dropped rather than passed on.
359
+ const VALID_UDID_RE = /^[A-Fa-f0-9-]+$/;
360
+
361
+ /**
362
+ * The iPhones devicectl can reach now, from `devicectl list devices
363
+ * --json-output` JSON. devicectl lists every device it ever paired with and
364
+ * simulators too: keep physical ones whose tunnel is not "unavailable".
365
+ *
366
+ * @returns {{ name: string, udid: string, version: string }[]}
367
+ */
368
+ function parseDevicectlDevices(json) {
369
+ return (json?.result?.devices || []).filter(d => d.hardwareProperties?.reality === "physical").filter(d => d.connectionProperties?.tunnelState !== "unavailable").filter(d => !d.hardwareProperties?.platform || d.hardwareProperties.platform === "iOS").map(d => ({
370
+ name: d.deviceProperties?.name ?? "iPhone",
371
+ udid: d.hardwareProperties?.udid ?? d.identifier,
372
+ version: d.deviceProperties?.osVersionNumber ?? ""
373
+ })).filter(d => typeof d.udid === "string" && VALID_UDID_RE.test(d.udid));
374
+ }
375
+
376
+ /**
377
+ * `xcrun devicectl list devices` into a temp file (Apple's scripting
378
+ * guidance), parsed. The file goes in a fresh owner-only folder from
379
+ * mkdtemp, so no other user can predict or pre-create its path.
380
+ */
381
+ function runDevicectlList({
382
+ exec = _child_process.execFileSync
383
+ } = {}) {
384
+ const dir = _fs.default.mkdtempSync(_path.default.join(_os.default.tmpdir(), "catalyst-devicectl-"));
385
+ const file = _path.default.join(dir, "devices.json");
386
+ try {
387
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - fixed argv.
388
+ exec("xcrun", ["devicectl", "list", "devices", "--json-output", file], {
389
+ stdio: ["ignore", "ignore", "ignore"],
390
+ timeout: 30000
391
+ });
392
+ return JSON.parse(_fs.default.readFileSync(file, "utf8"));
393
+ } finally {
394
+ _fs.default.rmSync(dir, {
395
+ recursive: true,
396
+ force: true
397
+ });
398
+ }
399
+ }
400
+
401
+ /** Connected iPhones; [] when devicectl fails (Xcode older than 15, nothing paired). */
402
+ function listConnectedIphones({
403
+ list = runDevicectlList,
404
+ log = () => {}
405
+ } = {}) {
406
+ try {
407
+ return parseDevicectlDevices(list());
408
+ } catch (error) {
409
+ log(`devicectl could not list devices (${error.message})`, "info");
410
+ return [];
411
+ }
412
+ }
413
+ const deviceLabel = device => `${device.name}${device.version ? ` (iOS ${device.version})` : ""}`;
414
+
415
+ /**
416
+ * Simulator or connected iPhone. The simulator is the default; `--device`
417
+ * picks the iPhone (one: that one; several: ios.deviceUDID, else a choice in
418
+ * a terminal, else NATIVE-005). Without the flag, a terminal with an iPhone
419
+ * connected is asked, defaulting to the last pick. A connected iPhone never
420
+ * wins on its own.
421
+ *
422
+ * @param {object} options
423
+ * @param {boolean} [options.device] - `--device`
424
+ * @param {{ name: string, udid: string, version?: string }[]} options.devices - connected iPhones
425
+ * @param {string} [options.simulatorName] - for the choice label, when known
426
+ * @param {{ target: string, udid?: string } | null} [options.last] - getLastTarget("ios")
427
+ * @param {string} [options.configuredUdid] - WEBVIEW_CONFIG.ios.deviceUDID
428
+ * @param {boolean} [options.isTTY]
429
+ * @param {typeof selectChoice} [options.select]
430
+ * @returns {Promise<{ target: "simulator" } | { target: "device", udid: string, name: string }>}
431
+ */
432
+ async function chooseIosTarget({
433
+ device: deviceFlag = false,
434
+ devices = [],
435
+ simulatorName,
436
+ last = null,
437
+ configuredUdid,
438
+ isTTY = (0, _utils.isInteractive)(),
439
+ select = _utils.select
440
+ } = {}) {
441
+ const asTarget = d => ({
442
+ target: "device",
443
+ udid: d.udid,
444
+ name: d.name
445
+ });
446
+ const lastDevice = last?.target === "device" ? devices.find(d => d.udid === last.udid) : null;
447
+ if (deviceFlag) {
448
+ if (!devices.length) {
449
+ throw (0, _utils.nativeError)("IOS-014", "--device was passed but devicectl lists no connected iPhone");
450
+ }
451
+ if (devices.length === 1) return asTarget(devices[0]);
452
+ const configured = configuredUdid ? devices.find(d => d.udid === configuredUdid) : null;
453
+ if (configured) return asTarget(configured);
454
+ if (!isTTY) {
455
+ throw (0, _utils.needsAnswerError)(`${devices.length} iPhones are connected. Which one should the app run on`, {
456
+ hint: "Connect only one iPhone, or set WEBVIEW_CONFIG.ios.deviceUDID to the one to use, or run the build in a terminal to pick."
457
+ });
458
+ }
459
+ const udid = await select("Which iPhone", devices.map(d => ({
460
+ label: deviceLabel(d),
461
+ value: d.udid,
462
+ hint: d.udid
463
+ })), {
464
+ defaultValue: lastDevice?.udid,
465
+ isTTY
466
+ });
467
+ return asTarget(devices.find(d => d.udid === udid));
468
+ }
469
+ if (!isTTY || !devices.length) return {
470
+ target: "simulator"
471
+ };
472
+ const choice = await select("Run the app on", [{
473
+ label: simulatorName ? `${simulatorName} simulator` : "Simulator",
474
+ value: "simulator"
475
+ }, ...devices.map(d => ({
476
+ label: deviceLabel(d),
477
+ value: d.udid,
478
+ hint: "connected"
479
+ }))], {
480
+ defaultValue: lastDevice ? lastDevice.udid : "simulator",
481
+ isTTY
482
+ });
483
+ if (choice === "simulator") return {
484
+ target: "simulator"
485
+ };
486
+ return asTarget(devices.find(d => d.udid === choice));
487
+ }
488
+
489
+ /**
490
+ * The host a debug build reaches the dev server on. The simulator shares the
491
+ * Mac's network, so localhost. An iPhone uses the Mac's Bonjour name
492
+ * (`<LocalHostName>.local`), else its LAN IP; IOS-015 when there is neither.
493
+ * It goes into the build only, never into config.json.
494
+ *
495
+ * @param {{ target: string, name?: string } | null} target
496
+ * @param {object} [options]
497
+ * @param {() => ({ host: string, source: string } | null)} [options.reachableHost]
498
+ * @param {(message: string, type?: string) => void} [options.log]
499
+ */
500
+ function devHostFor(target, {
501
+ reachableHost = _setupServer.getDeviceReachableHost,
502
+ log = () => {}
503
+ } = {}) {
504
+ if (target?.target !== "device") return "localhost";
505
+ const found = reachableHost();
506
+ if (!found) {
507
+ throw (0, _utils.nativeError)("IOS-015", `${target.name || "the iPhone"} cannot use localhost, and this Mac has no .local name or LAN address`);
508
+ }
509
+ if (found.source === "lan") {
510
+ log(`No Local Hostname on this Mac; ${target.name || "the iPhone"} loads the dev server from ${found.host}`, "info");
511
+ }
512
+ return found.host;
513
+ }
514
+
515
+ /**
516
+ * A debug build's target and dev server host: list the connected iPhones,
517
+ * choose (chooseIosTarget), find the simulator (ensureSimulator: configured,
518
+ * remembered, booted or newest, creating one only when there is no iPhone
519
+ * simulator), and remember the pick when a person or --device made it.
520
+ *
521
+ * @param {Record<string, any>} [options] - `flags` (parseNativeArgs), `iosConfig` (WEBVIEW_CONFIG.ios: simulatorName, deviceUDID), `cwd` (the app root, for .catalyst/local.json), `log`; the rest are seams for tests
522
+ * @returns {Promise<{ target: { target: "simulator" | "device", name: string, udid: string }, devHost: string }>}
523
+ */
524
+ async function prepareIosDebugTarget({
525
+ flags = {},
526
+ iosConfig = {},
527
+ cwd = process.cwd(),
528
+ isTTY = (0, _utils.isInteractive)(),
529
+ listDevices = listConnectedIphones,
530
+ last = (0, _localState.getLastTarget)("ios", {
531
+ cwd
532
+ }),
533
+ save = _localState.saveLastTarget,
534
+ select = _utils.select,
535
+ ensureRuntime = ensureIosRuntime,
536
+ ensureSim = ensureSimulator,
537
+ reachableHost = _setupServer.getDeviceReachableHost,
538
+ log = () => {}
539
+ } = {}) {
540
+ const devices = listDevices({
541
+ log
542
+ });
543
+ let target = await chooseIosTarget({
544
+ device: flags.device,
545
+ devices,
546
+ simulatorName: iosConfig.simulatorName || (last?.target === "simulator" ? last.name : undefined),
547
+ last,
548
+ configuredUdid: iosConfig.deviceUDID,
549
+ isTTY,
550
+ select
551
+ });
552
+ if (target.target === "simulator") {
553
+ const runtime = await ensureRuntime({
554
+ flags,
555
+ isTTY,
556
+ log
557
+ });
558
+ const simulator = await ensureSim({
559
+ runtime,
560
+ name: iosConfig.simulatorName,
561
+ last,
562
+ flags,
563
+ isTTY,
564
+ log
565
+ });
566
+ target = {
567
+ target: "simulator",
568
+ name: simulator.name,
569
+ udid: simulator.udid
570
+ };
571
+ }
572
+ const devHost = devHostFor(target, {
573
+ reachableHost,
574
+ log
575
+ });
576
+ if (flags.device || isTTY) save("ios", target, {
577
+ cwd
578
+ });
579
+ return {
580
+ target,
581
+ devHost
582
+ };
583
+ }
@@ -29,9 +29,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
29
29
  ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor
30
30
 
31
31
  // Code signing
32
- CODE_SIGN_IDENTITY = Apple Development
33
- CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer
34
- CODE_SIGN_STYLE = Manual
32
+ CODE_SIGN_STYLE = Automatic
35
33
 
36
34
  // Module settings
37
35
  CLANG_ENABLE_MODULES = NO
@@ -2,6 +2,8 @@
2
2
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
3
  <plist version="1.0">
4
4
  <dict>
5
+ <key>ITSAppUsesNonExemptEncryption</key>
6
+ <false/>
5
7
  <key>NSAppTransportSecurity</key>
6
8
  <dict>
7
9
  <key>NSAllowsArbitraryLoads</key>
@@ -0,0 +1,40 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>NSPrivacyTracking</key>
6
+ <false/>
7
+ <key>NSPrivacyTrackingDomains</key>
8
+ <array/>
9
+ <key>NSPrivacyCollectedDataTypes</key>
10
+ <array/>
11
+ <key>NSPrivacyAccessedAPITypes</key>
12
+ <array>
13
+ <dict>
14
+ <key>NSPrivacyAccessedAPIType</key>
15
+ <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
16
+ <key>NSPrivacyAccessedAPITypeReasons</key>
17
+ <array>
18
+ <string>CA92.1</string>
19
+ </array>
20
+ </dict>
21
+ <dict>
22
+ <key>NSPrivacyAccessedAPIType</key>
23
+ <string>NSPrivacyAccessedAPICategorySystemBootTime</string>
24
+ <key>NSPrivacyAccessedAPITypeReasons</key>
25
+ <array>
26
+ <string>35F9.1</string>
27
+ </array>
28
+ </dict>
29
+ <dict>
30
+ <key>NSPrivacyAccessedAPIType</key>
31
+ <string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
32
+ <key>NSPrivacyAccessedAPITypeReasons</key>
33
+ <array>
34
+ <string>C617.1</string>
35
+ <string>3B52.1</string>
36
+ </array>
37
+ </dict>
38
+ </array>
39
+ </dict>
40
+ </plist>
@@ -2,8 +2,6 @@
2
2
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
3
  <plist version="1.0">
4
4
  <dict>
5
- <key>aps-environment</key>
6
- <string>development</string>
7
5
  <key>keychain-access-groups</key>
8
6
  <array>
9
7
  <string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>