UNPKG

38 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports._downloadSimulatorAppAsync = _downloadSimulatorAppAsync;
7exports.activateSimulatorWindowAsync = activateSimulatorWindowAsync;
8exports.closeSimulatorAppAsync = closeSimulatorAppAsync;
9exports.doesExpoClientNeedUpdatedAsync = doesExpoClientNeedUpdatedAsync;
10Object.defineProperty(exports, "ensureSimulatorAppRunningAsync", {
11 enumerable: true,
12 get: function () {
13 return _ensureSimulatorAppRunningAsync().ensureSimulatorAppRunningAsync;
14 }
15});
16exports.ensureSimulatorOpenAsync = ensureSimulatorOpenAsync;
17exports.ensureXcodeCommandLineToolsInstalledAsync = ensureXcodeCommandLineToolsInstalledAsync;
18exports.ensureXcodeInstalledAsync = ensureXcodeInstalledAsync;
19exports.expoVersionOnSimulatorAsync = expoVersionOnSimulatorAsync;
20exports.installExpoOnSimulatorAsync = installExpoOnSimulatorAsync;
21exports.isExpoClientInstalledOnSimulatorAsync = isExpoClientInstalledOnSimulatorAsync;
22exports.isPlatformSupported = isPlatformSupported;
23exports.isSimulatorBootedAsync = isSimulatorBootedAsync;
24exports.isSimulatorInstalledAsync = isSimulatorInstalledAsync;
25exports.openProjectAsync = openProjectAsync;
26exports.openWebProjectAsync = openWebProjectAsync;
27exports.promptForSimulatorAsync = promptForSimulatorAsync;
28exports.resolveApplicationIdAsync = resolveApplicationIdAsync;
29exports.sortDefaultDeviceToBeginningAsync = sortDefaultDeviceToBeginningAsync;
30exports.streamLogsAsync = streamLogsAsync;
31exports.uninstallExpoAppFromSimulatorAsync = uninstallExpoAppFromSimulatorAsync;
32exports.upgradeExpoAsync = upgradeExpoAsync;
33exports.waitForExpoClientInstalledOnSimulatorAsync = waitForExpoClientInstalledOnSimulatorAsync;
34exports.waitForExpoClientUninstalledOnSimulatorAsync = waitForExpoClientUninstalledOnSimulatorAsync;
35function _config() {
36 const data = require("@expo/config");
37 _config = function () {
38 return data;
39 };
40 return data;
41}
42function _configPlugins() {
43 const data = require("@expo/config-plugins");
44 _configPlugins = function () {
45 return data;
46 };
47 return data;
48}
49function osascript() {
50 const data = _interopRequireWildcard(require("@expo/osascript"));
51 osascript = function () {
52 return data;
53 };
54 return data;
55}
56function _plist() {
57 const data = _interopRequireDefault(require("@expo/plist"));
58 _plist = function () {
59 return data;
60 };
61 return data;
62}
63function _spawnAsync() {
64 const data = _interopRequireDefault(require("@expo/spawn-async"));
65 _spawnAsync = function () {
66 return data;
67 };
68 return data;
69}
70function _chalk() {
71 const data = _interopRequireDefault(require("chalk"));
72 _chalk = function () {
73 return data;
74 };
75 return data;
76}
77function _child_process() {
78 const data = require("child_process");
79 _child_process = function () {
80 return data;
81 };
82 return data;
83}
84function _fsExtra() {
85 const data = _interopRequireDefault(require("fs-extra"));
86 _fsExtra = function () {
87 return data;
88 };
89 return data;
90}
91function _path() {
92 const data = _interopRequireDefault(require("path"));
93 _path = function () {
94 return data;
95 };
96 return data;
97}
98function _prompts() {
99 const data = _interopRequireDefault(require("prompts"));
100 _prompts = function () {
101 return data;
102 };
103 return data;
104}
105function _semver() {
106 const data = _interopRequireDefault(require("semver"));
107 _semver = function () {
108 return data;
109 };
110 return data;
111}
112function _ensureSimulatorAppRunningAsync() {
113 const data = require("./apple/utils/ensureSimulatorAppRunningAsync");
114 _ensureSimulatorAppRunningAsync = function () {
115 return data;
116 };
117 return data;
118}
119function _waitForActionAsync() {
120 const data = require("./apple/utils/waitForActionAsync");
121 _waitForActionAsync = function () {
122 return data;
123 };
124 return data;
125}
126function _internal() {
127 const data = require("./internal");
128 _internal = function () {
129 return data;
130 };
131 return data;
132}
133function _profileMethod() {
134 const data = require("./utils/profileMethod");
135 _profileMethod = function () {
136 return data;
137 };
138 return data;
139}
140function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
141function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
142function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
143let _lastUrl = null;
144const EXPO_GO_BUNDLE_IDENTIFIER = 'host.exp.Exponent';
145const SUGGESTED_XCODE_VERSION = `${_internal().Xcode.minimumVersion}.0`;
146const INSTALL_WARNING_TIMEOUT = 60 * 1000;
147function isPlatformSupported() {
148 return process.platform === 'darwin';
149}
150
151/**
152 * Ensure Xcode is installed an recent enough to be used with Expo.
153 *
154 * @return true when Xcode is installed, false when the process should end.
155 */
156async function ensureXcodeInstalledAsync() {
157 const promptToOpenAppStoreAsync = async message => {
158 // This prompt serves no purpose accept informing the user what to do next, we could just open the App Store but it could be confusing if they don't know what's going on.
159 const confirm = await _internal().Prompts.confirmAsync({
160 initial: true,
161 message
162 });
163 if (confirm) {
164 _internal().Logger.global.info(`Going to the App Store, re-run Expo when Xcode is finished installing.`);
165 _internal().Xcode.openAppStore(_internal().Xcode.appStoreId);
166 }
167 };
168 const version = (0, _profileMethod().profileMethod)(_internal().Xcode.getXcodeVersion)();
169 if (!version) {
170 // Almost certainly Xcode isn't installed.
171 await promptToOpenAppStoreAsync(`Xcode needs to be installed (don't worry, you won't have to use it), would you like to continue to the App Store?`);
172 return false;
173 }
174 if (!_semver().default.valid(version)) {
175 // Not sure why this would happen, if it does we should add a more confident error message.
176 _internal().Logger.global.error(`Xcode version is in an unknown format: ${version}`);
177 return false;
178 }
179 if (_semver().default.lt(version, SUGGESTED_XCODE_VERSION)) {
180 // Xcode version is too old.
181 await promptToOpenAppStoreAsync(`Xcode (${version}) needs to be updated to at least version ${_internal().Xcode.minimumVersion}, would you like to continue to the App Store?`);
182 return false;
183 }
184 return true;
185}
186let _isXcodeCLIInstalled = null;
187async function ensureXcodeCommandLineToolsInstalledAsync() {
188 // NOTE(Bacon): See `isSimulatorInstalledAsync` for more info on why we cache this value.
189 if (_isXcodeCLIInstalled != null) {
190 return _isXcodeCLIInstalled;
191 }
192 const _ensureXcodeCommandLineToolsInstalledAsync = async () => {
193 if (!(await ensureXcodeInstalledAsync())) {
194 // Need Xcode to install the CLI afaict
195 return false;
196 } else if (await _internal().SimControl.isXcrunInstalledAsync()) {
197 // Run this second to ensure the Xcode version check is run.
198 return true;
199 }
200 async function pendingAsync() {
201 if (await _internal().SimControl.isXcrunInstalledAsync()) {
202 return true;
203 } else {
204 await (0, _internal().delayAsync)(100);
205 return await pendingAsync();
206 }
207 }
208
209 // This prompt serves no purpose accept informing the user what to do next, we could just open the App Store but it could be confusing if they don't know what's going on.
210 const confirm = await _internal().Prompts.confirmAsync({
211 initial: true,
212 message: `Xcode ${_chalk().default.bold`Command Line Tools`} needs to be installed (requires ${_chalk().default.bold`sudo`}), continue?`
213 });
214 if (!confirm) {
215 return false;
216 }
217 try {
218 await (0, _spawnAsync().default)('sudo', ['xcode-select', '--install'
219 // TODO: Is there any harm in skipping this?
220 // '--switch', '/Applications/Xcode.app'
221 ]);
222 // Most likely the user will cancel the process, but if they don't this will continue checking until the CLI is available.
223 await pendingAsync();
224 return true;
225 } catch {
226 // TODO: Figure out why this might get called (cancel early, network issues, server problems)
227 // TODO: Handle me
228 }
229 return false;
230 };
231 _isXcodeCLIInstalled = await _ensureXcodeCommandLineToolsInstalledAsync();
232 return _isXcodeCLIInstalled;
233}
234async function getSimulatorAppIdAsync() {
235 let result;
236 try {
237 result = (await osascript().execAsync('id of app "Simulator"')).trim();
238 } catch {
239 // This error may occur in CI where the users intends to install just the simulators but no Xcode.
240 return null;
241 }
242 return result;
243}
244let _isSimulatorInstalled = null;
245
246// Simulator installed
247async function isSimulatorInstalledAsync() {
248 if (_isSimulatorInstalled != null) {
249 return _isSimulatorInstalled;
250 }
251 // NOTE(Bacon): This method can take upwards of 1-2s to run so we should cache the results per process.
252 // If the user installs Xcode while expo start is running, they'll need to restart
253 // the process for the command to work properly.
254 // This is better than waiting 1-2s every time you want to open the app on iOS.
255 const _isSimulatorInstalledAsync = async () => {
256 // Check to ensure Xcode and its CLI are installed and up to date.
257 if (!(await ensureXcodeCommandLineToolsInstalledAsync())) {
258 return false;
259 }
260 // TODO: extract into ensureSimulatorInstalled method
261
262 const result = await getSimulatorAppIdAsync();
263 if (!result) {
264 // This error may occur in CI where the users intends to install just the simulators but no Xcode.
265 _internal().Logger.global.error("Can't determine id of Simulator app; the Simulator is most likely not installed on this machine. Run `sudo xcode-select -s /Applications/Xcode.app`");
266 return false;
267 }
268 if (result !== 'com.apple.iphonesimulator' && result !== 'com.apple.CoreSimulator.SimulatorTrampoline') {
269 // TODO: FYI
270 _internal().Logger.global.warn("Simulator is installed but is identified as '" + result + "'; don't know what that is.");
271 return false;
272 }
273
274 // make sure we can run simctl
275 try {
276 await _internal().SimControl.simctlAsync(['help']);
277 } catch (e) {
278 if (e.isXDLError) {
279 _internal().Logger.global.error(e.toString());
280 } else {
281 _internal().Logger.global.warn(`Unable to run simctl: ${e.toString()}`);
282 _internal().Logger.global.error('xcrun may not be configured correctly. Try running `sudo xcode-select --reset` and running this again.');
283 }
284 return false;
285 }
286 return true;
287 };
288 _isSimulatorInstalled = await _isSimulatorInstalledAsync();
289 return _isSimulatorInstalled;
290}
291
292/**
293 * Ensure a simulator is booted and the Simulator app is opened.
294 * This is where any timeout related error handling should live.
295 */
296async function ensureSimulatorOpenAsync({
297 udid,
298 osType
299} = {}, tryAgain = true) {
300 // Use a default simulator if none was specified
301 if (!udid) {
302 // If a simulator is open, side step the entire booting sequence.
303 const simulatorOpenedByApp = await getBestBootedSimulatorAsync({
304 osType
305 });
306 if (simulatorOpenedByApp) {
307 return simulatorOpenedByApp;
308 }
309
310 // Otherwise, find the best possible simulator from user defaults and continue
311 udid = await getBestUnbootedSimulatorAsync({
312 osType
313 });
314 }
315 const bootedDevice = await (0, _profileMethod().profileMethod)(_internal().SimControl.waitForDeviceToBootAsync)({
316 udid
317 });
318 if (!bootedDevice) {
319 // Give it a second chance, this might not be needed but it could potentially lead to a better UX on slower devices.
320 if (tryAgain) {
321 return await ensureSimulatorOpenAsync({
322 udid,
323 osType
324 }, false);
325 }
326 // TODO: We should eliminate all needs for a timeout error, it's bad UX to get an error about the simulator not starting while the user can clearly see it starting on their slow computer.
327 throw new (_waitForActionAsync().TimeoutError)(`Simulator didn't boot fast enough. Try opening Simulator first, then running your app.`);
328 }
329 return bootedDevice;
330}
331async function getBestBootedSimulatorAsync({
332 osType
333}) {
334 var _simulatorOpenedByApp;
335 let simulatorOpenedByApp;
336 if (_internal().CoreSimulator.isEnabled()) {
337 simulatorOpenedByApp = await _internal().CoreSimulator.getDeviceInfoAsync().catch(() => null);
338 } else {
339 const simulatorDeviceInfo = await _internal().SimControl.listAsync('devices');
340 const devices = Object.values(simulatorDeviceInfo.devices).reduce((prev, runtime) => {
341 return prev.concat(runtime.filter(device => device.state === 'Booted'));
342 }, []);
343 simulatorOpenedByApp = devices[0];
344 }
345
346 // This should prevent opening a second simulator in the chance that default
347 // simulator doesn't match what the Simulator app would open by default.
348 if ((_simulatorOpenedByApp = simulatorOpenedByApp) !== null && _simulatorOpenedByApp !== void 0 && _simulatorOpenedByApp.udid && (!osType || osType && simulatorOpenedByApp.osType === osType)) {
349 return simulatorOpenedByApp;
350 }
351 return null;
352}
353async function getBestUnbootedSimulatorAsync({
354 osType
355}) {
356 const defaultUdid = _getDefaultSimulatorDeviceUDID();
357 if (defaultUdid && !osType) {
358 return defaultUdid;
359 }
360 const simulators = await getSelectableSimulatorsAsync({
361 osType
362 });
363 if (!simulators.length) {
364 // TODO: Prompt to install the simulators
365 throw new Error(`No ${osType || 'iOS'} devices available in Simulator.app`);
366 }
367
368 // If the default udid is defined, then check to ensure its osType matches the required os.
369 if (defaultUdid) {
370 const defaultSimulator = simulators.find(device => device.udid === defaultUdid);
371 if ((defaultSimulator === null || defaultSimulator === void 0 ? void 0 : defaultSimulator.osType) === osType) {
372 return defaultUdid;
373 }
374 }
375
376 // Return first selectable device.
377 return simulators[0].udid;
378}
379async function getBestSimulatorAsync({
380 osType
381}) {
382 const simulatorOpenedByApp = await getBestBootedSimulatorAsync({
383 osType
384 });
385 if (simulatorOpenedByApp) {
386 return simulatorOpenedByApp.udid;
387 }
388 return await getBestUnbootedSimulatorAsync({
389 osType
390 });
391}
392
393/**
394 * Get all simulators supported by Expo (iOS only).
395 */
396async function getSelectableSimulatorsAsync({
397 osType = 'iOS'
398} = {}) {
399 const simulators = await _internal().SimControl.listSimulatorDevicesAsync();
400 return simulators.filter(device => device.isAvailable && device.osType === osType);
401}
402
403// TODO: Delete
404async function getBootedSimulatorsAsync() {
405 const simulators = await _internal().SimControl.listSimulatorDevicesAsync();
406 return simulators.filter(device => device.state === 'Booted');
407}
408
409// TODO: Delete
410async function isSimulatorBootedAsync({
411 udid
412} = {}) {
413 // Simulators can be booted even if the app isn't running :(
414 const devices = await getBootedSimulatorsAsync();
415 if (udid) {
416 var _devices$find;
417 return (_devices$find = devices.find(bootedDevice => bootedDevice.udid === udid)) !== null && _devices$find !== void 0 ? _devices$find : null;
418 } else {
419 var _devices$;
420 return (_devices$ = devices[0]) !== null && _devices$ !== void 0 ? _devices$ : null;
421 }
422}
423function _getDefaultSimulatorDeviceUDID() {
424 try {
425 const defaultDeviceUDID = (0, _child_process().execSync)(`defaults read com.apple.iphonesimulator CurrentDeviceUDID`, {
426 stdio: 'pipe'
427 }).toString();
428 return defaultDeviceUDID.trim();
429 } catch {
430 return null;
431 }
432}
433async function activateSimulatorWindowAsync() {
434 // TODO: Focus the individual window
435 return await osascript().execAsync(`tell application "Simulator" to activate`);
436}
437async function closeSimulatorAppAsync() {
438 return await osascript().execAsync('tell application "Simulator" to quit');
439}
440async function isExpoClientInstalledOnSimulatorAsync({
441 udid
442}) {
443 return !!(await _internal().SimControl.getContainerPathAsync({
444 udid,
445 bundleIdentifier: EXPO_GO_BUNDLE_IDENTIFIER
446 }));
447}
448async function waitForExpoClientInstalledOnSimulatorAsync({
449 udid
450}) {
451 if (await isExpoClientInstalledOnSimulatorAsync({
452 udid
453 })) {
454 return true;
455 } else {
456 await (0, _internal().delayAsync)(100);
457 return await waitForExpoClientInstalledOnSimulatorAsync({
458 udid
459 });
460 }
461}
462async function waitForExpoClientUninstalledOnSimulatorAsync({
463 udid
464}) {
465 if (!(await isExpoClientInstalledOnSimulatorAsync({
466 udid
467 }))) {
468 return true;
469 } else {
470 await (0, _internal().delayAsync)(100);
471 return await waitForExpoClientInstalledOnSimulatorAsync({
472 udid
473 });
474 }
475}
476async function expoVersionOnSimulatorAsync({
477 udid
478}) {
479 const localPath = await _internal().SimControl.getContainerPathAsync({
480 udid,
481 bundleIdentifier: EXPO_GO_BUNDLE_IDENTIFIER
482 });
483 if (!localPath) {
484 return null;
485 }
486 const regex = /Exponent-([0-9.]+).*\.app$/;
487 const regexMatch = regex.exec(localPath);
488 if (!regexMatch) {
489 return null;
490 }
491 let matched = regexMatch[1];
492 // If the value is matched like 1.0.0. then remove the trailing dot.
493 if (matched.endsWith('.')) {
494 matched = matched.substr(0, matched.length - 1);
495 }
496 return matched;
497}
498async function doesExpoClientNeedUpdatedAsync(simulator, sdkVersion) {
499 var _clientForSdk$version;
500 // Test that upgrading works by returning true
501 // return true;
502 const versions = await (0, _profileMethod().profileMethod)(_internal().Versions.versionsAsync)();
503 const clientForSdk = await (0, _profileMethod().profileMethod)(getClientForSDK)(sdkVersion);
504 const latestVersionForSdk = (_clientForSdk$version = clientForSdk === null || clientForSdk === void 0 ? void 0 : clientForSdk.version) !== null && _clientForSdk$version !== void 0 ? _clientForSdk$version : versions.iosVersion;
505 const installedVersion = await expoVersionOnSimulatorAsync(simulator);
506 if (installedVersion && _semver().default.lt(installedVersion, latestVersionForSdk)) {
507 return true;
508 }
509 return false;
510}
511
512// If specific URL given just always download it and don't use cache
513async function _downloadSimulatorAppAsync(url, downloadProgressCallback) {
514 if (!url) {
515 const versions = await _internal().Versions.versionsAsync();
516 url = versions.iosUrl;
517 }
518 const filename = _path().default.parse(url).name;
519 const dir = _path().default.join(simulatorCacheDirectory(), `${filename}.app`);
520 if (await _fsExtra().default.pathExists(dir)) {
521 const filesInDir = await _fsExtra().default.readdir(dir);
522 if (filesInDir.length > 0) {
523 return dir;
524 } else {
525 _fsExtra().default.removeSync(dir);
526 }
527 }
528 _fsExtra().default.mkdirpSync(dir);
529 try {
530 await (0, _internal().downloadAppAsync)(url, dir, {
531 extract: true
532 }, downloadProgressCallback);
533 } catch (e) {
534 _fsExtra().default.removeSync(dir);
535 throw e;
536 }
537 return dir;
538}
539
540// url: Optional URL of Exponent.app tarball to download
541async function installExpoOnSimulatorAsync({
542 url,
543 simulator,
544 version
545}) {
546 let warningTimer;
547 const setWarningTimer = () => {
548 if (warningTimer) {
549 clearTimeout(warningTimer);
550 }
551 return setTimeout(() => {
552 _internal().Logger.global.info('');
553 _internal().Logger.global.info('This download is taking longer than expected. You can also try downloading the clients from the website at https://expo.dev/tools');
554 }, INSTALL_WARNING_TIMEOUT);
555 };
556 _internal().Logger.notifications.info({
557 code: _internal().LoadingEvent.START_PROGRESS_BAR
558 }, 'Downloading the Expo Go app [:bar] :percent :etas');
559 warningTimer = setWarningTimer();
560 const dir = await _downloadSimulatorAppAsync(url, progress => {
561 _internal().Logger.notifications.info({
562 code: _internal().LoadingEvent.TICK_PROGRESS_BAR
563 }, progress);
564 });
565 _internal().Logger.notifications.info({
566 code: _internal().LoadingEvent.STOP_PROGRESS_BAR
567 });
568 const message = version ? `Installing Expo Go ${version} on ${simulator.name}` : `Installing Expo Go on ${simulator.name}`;
569 _internal().Logger.notifications.info({
570 code: _internal().LoadingEvent.START_LOADING
571 }, message);
572 warningTimer = setWarningTimer();
573 const result = await _internal().SimControl.installAsync({
574 udid: simulator.udid,
575 dir
576 });
577 _internal().Logger.notifications.info({
578 code: _internal().LoadingEvent.STOP_LOADING
579 });
580 clearTimeout(warningTimer);
581 return result;
582}
583async function uninstallExpoAppFromSimulatorAsync({
584 udid
585} = {}) {
586 try {
587 _internal().Logger.global.info('Uninstalling Expo Go from iOS simulator.');
588 await _internal().SimControl.uninstallAsync({
589 udid,
590 bundleIdentifier: EXPO_GO_BUNDLE_IDENTIFIER
591 });
592 } catch (e) {
593 var _e$message;
594 if (!((_e$message = e.message) !== null && _e$message !== void 0 && _e$message.includes('No devices are booted.'))) {
595 _internal().Logger.global.error(e);
596 throw e;
597 }
598 }
599}
600function simulatorCacheDirectory() {
601 const dotExpoHomeDirectory = _internal().UserSettings.dotExpoHomeDirectory();
602 const dir = _path().default.join(dotExpoHomeDirectory, 'ios-simulator-app-cache');
603 _fsExtra().default.mkdirpSync(dir);
604 return dir;
605}
606async function upgradeExpoAsync(options = {}) {
607 if (!(await isSimulatorInstalledAsync())) {
608 return false;
609 }
610 const simulator = await ensureSimulatorOpenAsync(options);
611 await uninstallExpoAppFromSimulatorAsync(simulator);
612 const installResult = await installExpoOnSimulatorAsync({
613 url: options.url,
614 version: options.version,
615 simulator
616 });
617 if (installResult.status !== 0) {
618 return false;
619 }
620 if (_lastUrl) {
621 _internal().Logger.global.info(`\u203A Opening ${_chalk().default.underline(_lastUrl)} in Expo Go`);
622 await Promise.all([
623 // Open the Simulator.app app
624 (0, _ensureSimulatorAppRunningAsync().ensureSimulatorAppRunningAsync)(simulator),
625 // Launch the project in the simulator, this can be parallelized for some reason.
626 _internal().SimControl.openURLAsync({
627 udid: simulator.udid,
628 url: _lastUrl
629 })]);
630 _lastUrl = null;
631 }
632 return true;
633}
634async function openUrlInSimulatorSafeAsync({
635 url,
636 udid,
637 isDetached = false,
638 sdkVersion,
639 devClient = false,
640 projectRoot,
641 exp,
642 skipNativeLogs = false
643}) {
644 if (!exp) {
645 exp = (0, _profileMethod().profileMethod)(_config().getConfig)(projectRoot, {
646 skipSDKVersionRequirement: true
647 }).exp;
648 }
649 let simulator = null;
650 try {
651 simulator = await (0, _profileMethod().profileMethod)(ensureSimulatorOpenAsync)({
652 udid
653 });
654 } catch (error) {
655 return {
656 success: false,
657 msg: error.message
658 };
659 }
660 _internal().Logger.global.info(`\u203A Opening ${_chalk().default.underline(url)} on ${_chalk().default.bold(simulator.name)}`);
661 let bundleIdentifier = EXPO_GO_BUNDLE_IDENTIFIER;
662 try {
663 if (devClient) {
664 bundleIdentifier = await (0, _profileMethod().profileMethod)(_internal().BundleIdentifier.configureBundleIdentifierAsync)(projectRoot, exp);
665 await (0, _profileMethod().profileMethod)(assertDevClientInstalledAsync)(simulator, bundleIdentifier);
666 if (!skipNativeLogs) {
667 // stream logs before opening the client.
668 await streamLogsAsync({
669 udid: simulator.udid,
670 bundleIdentifier
671 });
672 }
673 } else if (_internal().Env.isInterstitiaLPageEnabled() && !devClient && (0, _internal().isDevClientPackageInstalled)(projectRoot)) {
674 await (0, _profileMethod().profileMethod)(ensureExpoClientInstalledAsync)(simulator, sdkVersion);
675 const devClientBundlerIdentifier = await (0, _profileMethod().profileMethod)(_internal().BundleIdentifier.configureBundleIdentifierAsync)(projectRoot, exp);
676 const isDevClientInstalled = await isDevClientInstalledAsync(simulator, devClientBundlerIdentifier);
677 if (isDevClientInstalled) {
678 // Everything is installed, we can present the interstitial page.
679 bundleIdentifier = ''; // it will open browser.
680 } else {
681 // The development build isn't available. So let's fall back to Expo Go.
682 _internal().Logger.global.warn(`\u203A The 'expo-dev-client' package is installed, but a development build isn't available.\nYour app will open in Expo Go instead. If you want to use the development build, please install it on the simulator first.\n${(0, _internal().learnMore)('https://docs.expo.dev/development/build/')}`);
683
684 // Generate a new deep link into Expo Go.
685 const newProjectUrl = await constructDeepLinkAsync(projectRoot, undefined, false, false);
686 if (!newProjectUrl) {
687 // This shouldn't happen.
688 throw Error('Could not generate a deep link for your project.');
689 }
690 url = newProjectUrl;
691 _internal().Logger.global.debug(`iOS project url: ${url}`);
692 _lastUrl = url;
693 }
694 } else if (!isDetached) {
695 await (0, _profileMethod().profileMethod)(ensureExpoClientInstalledAsync)(simulator, sdkVersion);
696 _lastUrl = url;
697 }
698 await Promise.all([
699 // Open the Simulator.app app, and bring it to the front
700 (0, _profileMethod().profileMethod)(async () => {
701 var _simulator;
702 await (0, _ensureSimulatorAppRunningAsync().ensureSimulatorAppRunningAsync)({
703 udid: (_simulator = simulator) === null || _simulator === void 0 ? void 0 : _simulator.udid
704 });
705 activateSimulatorWindowAsync();
706 }, 'parallel: ensureSimulatorAppRunningAsync')(),
707 // Launch the project in the simulator, this can be parallelized for some reason.
708 (0, _profileMethod().profileMethod)(_internal().SimControl.openURLAsync, 'parallel: openURLAsync')({
709 udid: simulator.udid,
710 url
711 })]);
712 } catch (e) {
713 if (e.status === 194) {
714 // An error was encountered processing the command (domain=NSOSStatusErrorDomain, code=-10814):
715 // The operation couldn’t be completed. (OSStatus error -10814.)
716 //
717 // This can be thrown when no app conforms to the URI scheme that we attempted to open.
718
719 return {
720 success: false,
721 msg: `Device ${simulator.name} (${simulator.udid}) has no app to handle the URI: ${url}`
722 };
723 }
724 if (e.isXDLError) {
725 // Hit some internal error, don't try again.
726 // This includes Xcode license errors
727 // Logger.global.error(e.message);
728 return {
729 success: false,
730 msg: `${e.toString()}`
731 };
732 }
733 return {
734 success: false,
735 msg: `${e.toString()}`
736 };
737 }
738 _internal().Analytics.logEvent('Open Url on Device', {
739 platform: 'ios'
740 });
741 return {
742 success: true,
743 device: simulator,
744 bundleIdentifier
745 };
746}
747async function assertDevClientInstalledAsync(simulator, bundleIdentifier) {
748 if (!(await _internal().SimControl.getContainerPathAsync({
749 udid: simulator.udid,
750 bundleIdentifier
751 }))) {
752 throw new Error(`The development client (${bundleIdentifier}) for this project is not installed. ` + `Please build and install the client on the simulator first.\n${(0, _internal().learnMore)('https://docs.expo.dev/clients/distribution-for-ios/#building-for-ios')}`);
753 }
754}
755async function isDevClientInstalledAsync(simulator, bundleIdentifier) {
756 try {
757 await assertDevClientInstalledAsync(simulator, bundleIdentifier);
758 } catch {
759 return false;
760 }
761 return true;
762}
763
764// Keep a list of simulator UDIDs so we can prevent asking multiple times if a user wants to upgrade.
765// This can prevent annoying interactions when they don't want to upgrade for whatever reason.
766const hasPromptedToUpgrade = {};
767async function ensureExpoClientInstalledAsync(simulator, sdkVersion) {
768 let isInstalled = await isExpoClientInstalledOnSimulatorAsync(simulator);
769 if (isInstalled && !hasPromptedToUpgrade[simulator.udid]) {
770 // Only prompt/check for updates once per simulator in a single run.
771 hasPromptedToUpgrade[simulator.udid] = true;
772 if (await (0, _profileMethod().profileMethod)(doesExpoClientNeedUpdatedAsync)(simulator, sdkVersion)) {
773 const confirm = await _internal().Prompts.confirmAsync({
774 initial: true,
775 message: `Expo Go on ${simulator.name} is outdated, would you like to upgrade?`
776 });
777 if (confirm) {
778 // TODO: Is there any downside to skipping the uninstall step?
779 // await uninstallExpoAppFromSimulatorAsync(simulator);
780 // await waitForExpoClientUninstalledOnSimulatorAsync(simulator);
781 isInstalled = false;
782 }
783 }
784 }
785 // If it's still "not installed" then install it (again).
786 if (!isInstalled) {
787 const iosClient = await getClientForSDK(sdkVersion);
788 await installExpoOnSimulatorAsync({
789 simulator,
790 ...iosClient
791 });
792 await waitForExpoClientInstalledOnSimulatorAsync(simulator);
793 }
794}
795async function getClientForSDK(sdkVersionString) {
796 if (!sdkVersionString) {
797 return null;
798 }
799 const sdkVersion = (await _internal().Versions.sdkVersionsAsync())[sdkVersionString];
800 if (!sdkVersion) {
801 return null;
802 }
803 return {
804 url: sdkVersion.iosClientUrl,
805 version: sdkVersion.iosClientVersion
806 };
807}
808async function resolveApplicationIdAsync(projectRoot) {
809 var _exp$ios;
810 // Check xcode project
811 try {
812 const bundleId = await _configPlugins().IOSConfig.BundleIdentifier.getBundleIdentifierFromPbxproj(projectRoot);
813 if (bundleId) {
814 return bundleId;
815 }
816 } catch {}
817
818 // Check Info.plist
819 try {
820 const infoPlistPath = _configPlugins().IOSConfig.Paths.getInfoPlistPath(projectRoot);
821 const data = await _plist().default.parse(_fsExtra().default.readFileSync(infoPlistPath, 'utf8'));
822 if (data.CFBundleIdentifier && !data.CFBundleIdentifier.startsWith('$(')) {
823 return data.CFBundleIdentifier;
824 }
825 } catch {}
826
827 // Check Expo config
828 const {
829 exp
830 } = (0, _config().getConfig)(projectRoot, {
831 skipSDKVersionRequirement: true
832 });
833 return (_exp$ios = exp.ios) === null || _exp$ios === void 0 ? void 0 : _exp$ios.bundleIdentifier;
834}
835async function constructDeepLinkAsync(projectRoot, scheme, devClient, shouldGenerateInterstitialPage = true) {
836 if (_internal().Env.isInterstitiaLPageEnabled() && !devClient && (0, _internal().isDevClientPackageInstalled)(projectRoot) && shouldGenerateInterstitialPage) {
837 return _internal().UrlUtils.constructLoadingUrlAsync(projectRoot, 'ios', 'localhost');
838 } else {
839 try {
840 return await _internal().UrlUtils.constructDeepLinkAsync(projectRoot, {
841 // Don't pass a `hostType` or ngrok will break.
842 scheme
843 });
844 } catch (e) {
845 if (devClient) {
846 return null;
847 }
848 throw e;
849 }
850 }
851}
852async function openProjectAsync({
853 projectRoot,
854 shouldPrompt,
855 devClient,
856 udid,
857 scheme,
858 skipNativeLogs,
859 applicationId
860}) {
861 var _device2;
862 if (!(await (0, _profileMethod().profileMethod)(isSimulatorInstalledAsync)())) {
863 return {
864 success: false,
865 error: 'Unable to verify Xcode and Simulator installation.'
866 };
867 }
868 const projectUrl = await constructDeepLinkAsync(projectRoot, scheme, devClient);
869 _internal().Logger.global.debug(`iOS project url: ${projectUrl}`);
870 const {
871 exp
872 } = (0, _config().getConfig)(projectRoot, {
873 skipSDKVersionRequirement: true
874 });
875 let device = null;
876 if (!udid && shouldPrompt) {
877 const devices = await getSelectableSimulatorsAsync();
878 device = await promptForSimulatorAsync(devices);
879 if (!device) {
880 return {
881 success: false,
882 error: 'escaped'
883 };
884 }
885 } else {
886 device = await ensureSimulatorOpenAsync({
887 udid
888 });
889 }
890
891 // No URL, and is devClient
892 if (!projectUrl) {
893 var _applicationId;
894 applicationId = (_applicationId = applicationId) !== null && _applicationId !== void 0 ? _applicationId : await resolveApplicationIdAsync(projectRoot);
895 _internal().Logger.global.debug(`Open iOS project from app id: ${applicationId}`);
896 if (!applicationId) {
897 return {
898 success: false,
899 error: 'Cannot resolve bundle identifier or URI scheme to open the native iOS app.\nBuild the native app with `expo run:ios` or `eas build -p ios`'
900 };
901 }
902 _internal().Logger.global.info(`\u203A Opening ${_chalk().default.underline(applicationId)} on ${_chalk().default.bold(device.name)}`);
903 const result = await _internal().SimControl.openBundleIdAsync({
904 udid: device.udid,
905 bundleIdentifier: applicationId
906 }).catch(error => {
907 if ('status' in error) {
908 return error;
909 }
910 throw error;
911 });
912 if (result.status === 0) {
913 var _device;
914 await (0, _ensureSimulatorAppRunningAsync().ensureSimulatorAppRunningAsync)({
915 udid: (_device = device) === null || _device === void 0 ? void 0 : _device.udid
916 });
917 activateSimulatorWindowAsync();
918 } else {
919 let errorMessage = `Couldn't open iOS app with ID "${applicationId}" on device "${device.name}".`;
920 if (result.status === 4) {
921 errorMessage += `\nThe app might not be installed, try installing it with: ${_chalk().default.bold(`expo run:ios -d ${device.udid}`)}`;
922 }
923 errorMessage += _chalk().default.gray(`\n${result.stderr}`);
924 return {
925 success: false,
926 error: errorMessage
927 };
928 }
929 return {
930 success: true,
931 udid: device.udid,
932 bundleIdentifier: applicationId,
933 // TODO: Remove this hack
934 url: ''
935 };
936 }
937 const result = await (0, _profileMethod().profileMethod)(openUrlInSimulatorSafeAsync)({
938 udid: (_device2 = device) === null || _device2 === void 0 ? void 0 : _device2.udid,
939 url: projectUrl,
940 sdkVersion: exp.sdkVersion,
941 isDetached: !!exp.isDetached,
942 devClient,
943 exp,
944 projectRoot,
945 skipNativeLogs
946 });
947 if (result.success) {
948 return {
949 success: true,
950 url: projectUrl,
951 udid: result.device.udid,
952 bundleIdentifier: result.bundleIdentifier
953 };
954 }
955 return {
956 success: result.success,
957 error: result.msg
958 };
959}
960async function streamLogsAsync({
961 bundleIdentifier,
962 udid
963}) {
964 if (_internal().SimControlLogs.isStreamingLogs(udid)) {
965 return;
966 }
967 const imageName = await _internal().SimControlLogs.getImageNameFromBundleIdentifierAsync(udid, bundleIdentifier);
968 if (imageName) {
969 // Attach simulator log observer
970 _internal().SimControlLogs.streamLogs({
971 pid: imageName,
972 udid
973 });
974 }
975}
976async function openWebProjectAsync({
977 projectRoot,
978 shouldPrompt
979}) {
980 var _device3;
981 if (!(await isSimulatorInstalledAsync())) {
982 return {
983 success: false,
984 error: 'Unable to verify Xcode and Simulator installation.'
985 };
986 }
987 const projectUrl = await _internal().Webpack.getUrlAsync(projectRoot);
988 if (projectUrl === null) {
989 return {
990 success: false,
991 error: `The web project has not been started yet`
992 };
993 }
994 let device = null;
995 if (shouldPrompt) {
996 const devices = await getSelectableSimulatorsAsync();
997 device = await promptForSimulatorAsync(devices);
998 if (!device) {
999 return {
1000 success: false,
1001 error: 'escaped'
1002 };
1003 }
1004 }
1005 const result = await openUrlInSimulatorSafeAsync({
1006 url: projectUrl,
1007 udid: (_device3 = device) === null || _device3 === void 0 ? void 0 : _device3.udid,
1008 isDetached: true,
1009 projectRoot
1010 });
1011 if (result.success) {
1012 // run out of sync
1013 activateSimulatorWindowAsync();
1014 return {
1015 success: true,
1016 url: projectUrl
1017 };
1018 }
1019 return {
1020 success: result.success,
1021 error: result.msg
1022 };
1023}
1024
1025/**
1026 * Sort the devices so the last simulator that was opened (user's default) is the first suggested.
1027 *
1028 * @param devices
1029 */
1030async function sortDefaultDeviceToBeginningAsync(devices, osType) {
1031 const defaultUdid = await getBestSimulatorAsync({
1032 osType
1033 });
1034 if (defaultUdid) {
1035 let iterations = 0;
1036 while (devices[0].udid !== defaultUdid && iterations < devices.length) {
1037 devices.push(devices.shift());
1038 iterations++;
1039 }
1040 }
1041 return devices;
1042}
1043async function promptForSimulatorAsync(devices, osType) {
1044 devices = await sortDefaultDeviceToBeginningAsync(devices, osType);
1045 // TODO: Bail on non-interactive
1046 const results = await promptForDeviceAsync(devices);
1047 return results ? devices.find(({
1048 udid
1049 }) => results === udid) : null;
1050}
1051async function promptForDeviceAsync(devices) {
1052 // TODO: provide an option to add or download more simulators
1053 // TODO: Add support for physical devices too.
1054
1055 // Pause interactions on the TerminalUI
1056 _internal().Prompts.pauseInteractions();
1057 const {
1058 value
1059 } = await (0, _prompts().default)({
1060 type: 'autocomplete',
1061 name: 'value',
1062 limit: 11,
1063 message: 'Select a simulator',
1064 choices: devices.map(item => {
1065 const isActive = item.state === 'Booted';
1066 const format = isActive ? _chalk().default.bold : text => text;
1067 return {
1068 title: `${format(item.name)} ${_chalk().default.dim(`(${item.osVersion})`)}`,
1069 value: item.udid
1070 };
1071 }),
1072 suggest: (input, choices) => {
1073 const regex = new RegExp(input, 'i');
1074 return choices.filter(choice => regex.test(choice.title));
1075 }
1076 });
1077
1078 // Resume interactions on the TerminalUI
1079 _internal().Prompts.resumeInteractions();
1080 return value;
1081}
1082//# sourceMappingURL=Simulator.js.map
\No newline at end of file