expo-dev-launcher 6.0.18 → 6.0.20

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/CHANGELOG.md CHANGED
@@ -10,6 +10,18 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 6.0.20 — 2025-12-05
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [iOS] Fix port scanning on pysical devices. ([#40824](https://github.com/expo/expo/pull/40824) by [@alanjhughes](https://github.com/alanjhughes))
18
+
19
+ ## 6.0.19 — 2025-12-04
20
+
21
+ ### 🐛 Bug fixes
22
+
23
+ - Restore config plugin `launchMode` support ([#41363](https://github.com/expo/expo/pull/41363) by [@gabrieldonadel](https://github.com/gabrieldonadel))
24
+
13
25
  ## 6.0.18 — 2025-11-17
14
26
 
15
27
  _This version does not introduce any user-facing changes._
@@ -20,21 +20,32 @@ expoModule {
20
20
  }
21
21
 
22
22
  group = "host.exp.exponent"
23
- version = "6.0.18"
23
+ version = "6.0.20"
24
24
 
25
25
  android {
26
26
  namespace "expo.modules.devlauncher"
27
27
  defaultConfig {
28
28
  versionCode 9
29
- versionName "6.0.18"
29
+ versionName "6.0.20"
30
30
  }
31
31
 
32
32
  buildTypes {
33
+ create("debugOptimized") {
34
+ initWith(buildTypes.debug)
35
+ matchingFallbacks += ["release"]
36
+ }
37
+
33
38
  buildTypes.each {
34
39
  it.buildConfigField 'String', 'VERSION', "\"${defaultConfig.versionName}\""
35
40
  }
36
41
  }
37
42
 
43
+ sourceSets {
44
+ debugOptimized {
45
+ setRoot 'src/debug'
46
+ }
47
+ }
48
+
38
49
  buildFeatures {
39
50
  buildConfig true
40
51
  viewBinding true
package/app.plugin.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./plugin/build/withDevLauncher');
@@ -51,7 +51,7 @@ abstract class DevLauncherPlugin : Plugin<Project> {
51
51
  return if (System.getenv("EX_UPDATES_NATIVE_DEBUG") != "1" && reactExtension != null) {
52
52
  reactExtension.debuggableVariants.get().any { it.equals(variant.name, ignoreCase = true) }
53
53
  } else {
54
- variant.buildType == "debug"
54
+ variant.buildType == "debug" || variant.buildType == "debugOptimized"
55
55
  }
56
56
  }
57
57
 
@@ -125,12 +125,15 @@ class DevLauncherViewModel: ObservableObject {
125
125
  // swiftlint:disable number_separator
126
126
  let portsToCheck = [8081, 8082, 8_083, 8084, 8085, 19000, 19001, 19002]
127
127
  // swiftlint:enable number_separator
128
- let baseAddress = "http://localhost"
129
128
 
129
+ let ipsToScan = NetworkUtilities.getIPAddressesToScan()
130
130
  await withTaskGroup(of: DevServer?.self) { group in
131
- for port in portsToCheck {
132
- group.addTask {
133
- await self.checkDevServer(url: "\(baseAddress):\(port)")
131
+ for ip in ipsToScan {
132
+ for port in portsToCheck {
133
+ group.addTask {
134
+ let baseAddress = ip == "localhost" ? "http://localhost" : "http://\(ip)"
135
+ return await self.checkDevServer(url: "\(baseAddress):\(port)")
136
+ }
134
137
  }
135
138
  }
136
139
 
@@ -0,0 +1,73 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ import Foundation
4
+ import Network
5
+
6
+ class NetworkUtilities {
7
+ // Same approach as expo-network just without throwing.
8
+ static func getLocalIPAddress() -> String? {
9
+ var address: String?
10
+
11
+ var ifaddr: UnsafeMutablePointer<ifaddrs>?
12
+ guard getifaddrs(&ifaddr) == 0 else { return nil }
13
+ guard let firstAddr = ifaddr else { return nil }
14
+
15
+ defer { freeifaddrs(ifaddr) }
16
+
17
+ for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
18
+ let interface = ifptr.pointee
19
+
20
+ let addrFamily = interface.ifa_addr.pointee.sa_family
21
+ if addrFamily == UInt8(AF_INET) {
22
+ let name = String(cString: interface.ifa_name)
23
+
24
+ if name == "en0" || name == "en1" {
25
+ var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
26
+ getnameinfo(
27
+ interface.ifa_addr,
28
+ socklen_t(interface.ifa_addr.pointee.sa_len),
29
+ &hostname,
30
+ socklen_t(hostname.count),
31
+ nil,
32
+ socklen_t(0),
33
+ NI_NUMERICHOST
34
+ )
35
+ address = String(cString: hostname)
36
+ break
37
+ }
38
+ }
39
+ }
40
+
41
+ return address
42
+ }
43
+
44
+ static func isSimulator() -> Bool {
45
+ #if targetEnvironment(simulator)
46
+ return true
47
+ #else
48
+ return false
49
+ #endif
50
+ }
51
+
52
+ static func getIPAddressesToScan() -> [String] {
53
+ if isSimulator() {
54
+ return ["localhost"]
55
+ }
56
+
57
+ guard let localIP = getLocalIPAddress() else {
58
+ return ["localhost"]
59
+ }
60
+
61
+ let components = localIP.split(separator: ".")
62
+ guard components.count == 4 else {
63
+ return ["localhost"]
64
+ }
65
+
66
+ let subnet = components.prefix(3).joined(separator: ".")
67
+
68
+ return [
69
+ "localhost",
70
+ "\(subnet).1"
71
+ ]
72
+ }
73
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expo-dev-launcher",
3
3
  "title": "Expo Development Launcher",
4
- "version": "6.0.18",
4
+ "version": "6.0.20",
5
5
  "description": "Pre-release version of the Expo development launcher package for testing.",
6
6
  "repository": {
7
7
  "type": "git",
@@ -15,11 +15,12 @@
15
15
  "license": "MIT",
16
16
  "homepage": "https://docs.expo.dev",
17
17
  "dependencies": {
18
- "expo-dev-menu": "7.0.17",
19
- "expo-manifests": "~1.0.9"
18
+ "ajv": "^8.11.0",
19
+ "expo-dev-menu": "7.0.18",
20
+ "expo-manifests": "~1.0.10"
20
21
  },
21
22
  "peerDependencies": {
22
23
  "expo": "*"
23
24
  },
24
- "gitHead": "36dd0d95ef851763ebe8c97c22afde3cb2f846cb"
25
+ "gitHead": "172a69f5f70c1d0e043e1532f924de97210cabc3"
25
26
  }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Type representing base dev launcher configuration.
3
+ */
4
+ export type PluginConfigType = PluginConfigOptionsByPlatform & PluginConfigOptions;
5
+ /**
6
+ * Type representing available configuration for each platform.
7
+ */
8
+ export type PluginConfigOptionsByPlatform = {
9
+ /**
10
+ * Type representing available configuration for Android dev launcher.
11
+ * @platform android
12
+ */
13
+ android?: PluginConfigOptions;
14
+ /**
15
+ * Type representing available configuration for iOS dev launcher.
16
+ * @platform ios
17
+ */
18
+ ios?: PluginConfigOptions;
19
+ };
20
+ /**
21
+ * Type representing available configuration for dev launcher.
22
+ */
23
+ export type PluginConfigOptions = {
24
+ /**
25
+ * Determines whether to launch the most recently opened project or navigate to the launcher screen.
26
+ *
27
+ * - `'most-recent'` - Attempt to launch directly into a previously opened project and if unable to connect,
28
+ * fall back to the launcher screen.
29
+ *
30
+ * - `'launcher'` - Opens the launcher screen.
31
+ *
32
+ * @default 'most-recent'
33
+ */
34
+ launchMode?: 'most-recent' | 'launcher';
35
+ /**
36
+ * @deprecated use the `launchMode` property instead
37
+ */
38
+ launchModeExperimental?: 'most-recent' | 'launcher';
39
+ };
40
+ /**
41
+ * @ignore
42
+ */
43
+ export declare function validateConfig<T>(config: T): PluginConfigType;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.validateConfig = validateConfig;
7
+ const ajv_1 = __importDefault(require("ajv"));
8
+ const schema = {
9
+ type: 'object',
10
+ properties: {
11
+ launchMode: {
12
+ type: 'string',
13
+ enum: ['most-recent', 'launcher'],
14
+ nullable: true,
15
+ },
16
+ launchModeExperimental: {
17
+ type: 'string',
18
+ enum: ['most-recent', 'launcher'],
19
+ nullable: true,
20
+ },
21
+ android: {
22
+ type: 'object',
23
+ properties: {
24
+ launchMode: {
25
+ type: 'string',
26
+ enum: ['most-recent', 'launcher'],
27
+ nullable: true,
28
+ },
29
+ launchModeExperimental: {
30
+ type: 'string',
31
+ enum: ['most-recent', 'launcher'],
32
+ nullable: true,
33
+ },
34
+ },
35
+ nullable: true,
36
+ },
37
+ ios: {
38
+ type: 'object',
39
+ properties: {
40
+ launchMode: {
41
+ type: 'string',
42
+ enum: ['most-recent', 'launcher'],
43
+ nullable: true,
44
+ },
45
+ launchModeExperimental: {
46
+ type: 'string',
47
+ enum: ['most-recent', 'launcher'],
48
+ nullable: true,
49
+ },
50
+ },
51
+ nullable: true,
52
+ },
53
+ },
54
+ };
55
+ /**
56
+ * @ignore
57
+ */
58
+ function validateConfig(config) {
59
+ const validate = new ajv_1.default({ allowUnionTypes: true }).compile(schema);
60
+ if (!validate(config)) {
61
+ throw new Error('Invalid expo-dev-launcher config: ' + JSON.stringify(validate.errors));
62
+ }
63
+ if (config.launchModeExperimental ||
64
+ config.ios?.launchModeExperimental ||
65
+ config.android?.launchModeExperimental) {
66
+ warnOnce('The `launchModeExperimental` property of expo-dev-launcher config plugin is deprecated and will be removed in a future SDK release. Use `launchMode` instead.');
67
+ }
68
+ return config;
69
+ }
70
+ const warnMap = {};
71
+ function warnOnce(message) {
72
+ if (!warnMap[message]) {
73
+ warnMap[message] = true;
74
+ console.warn(message);
75
+ }
76
+ }
@@ -0,0 +1,3 @@
1
+ import { PluginConfigType } from './pluginConfig';
2
+ declare const _default: import("expo/config-plugins").ConfigPlugin<PluginConfigType>;
3
+ export default _default;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config_plugins_1 = require("expo/config-plugins");
4
+ const pluginConfig_1 = require("./pluginConfig");
5
+ const pkg = require('expo-dev-launcher/package.json');
6
+ exports.default = (0, config_plugins_1.createRunOncePlugin)((config, props = {}) => {
7
+ (0, pluginConfig_1.validateConfig)(props);
8
+ const iOSLaunchMode = props.ios?.launchMode ??
9
+ props.launchMode ??
10
+ props.ios?.launchModeExperimental ??
11
+ props.launchModeExperimental;
12
+ if (iOSLaunchMode === 'launcher') {
13
+ config = (0, config_plugins_1.withInfoPlist)(config, (config) => {
14
+ config.modResults['DEV_CLIENT_TRY_TO_LAUNCH_LAST_BUNDLE'] = false;
15
+ return config;
16
+ });
17
+ }
18
+ const androidLaunchMode = props.android?.launchMode ??
19
+ props.launchMode ??
20
+ props.android?.launchModeExperimental ??
21
+ props.launchModeExperimental;
22
+ if (androidLaunchMode === 'launcher') {
23
+ config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
24
+ const mainApplication = config_plugins_1.AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
25
+ config_plugins_1.AndroidConfig.Manifest.addMetaDataItemToMainApplication(mainApplication, 'DEV_CLIENT_TRY_TO_LAUNCH_LAST_BUNDLE', false?.toString());
26
+ return config;
27
+ });
28
+ }
29
+ return config;
30
+ }, pkg.name, pkg.version);
@@ -0,0 +1 @@
1
+ module.exports = {};
@@ -0,0 +1,121 @@
1
+ import Ajv, { JSONSchemaType } from 'ajv';
2
+
3
+ /**
4
+ * Type representing base dev launcher configuration.
5
+ */
6
+ export type PluginConfigType = PluginConfigOptionsByPlatform & PluginConfigOptions;
7
+
8
+ /**
9
+ * Type representing available configuration for each platform.
10
+ */
11
+ export type PluginConfigOptionsByPlatform = {
12
+ /**
13
+ * Type representing available configuration for Android dev launcher.
14
+ * @platform android
15
+ */
16
+ android?: PluginConfigOptions;
17
+ /**
18
+ * Type representing available configuration for iOS dev launcher.
19
+ * @platform ios
20
+ */
21
+ ios?: PluginConfigOptions;
22
+ };
23
+
24
+ /**
25
+ * Type representing available configuration for dev launcher.
26
+ */
27
+ export type PluginConfigOptions = {
28
+ /**
29
+ * Determines whether to launch the most recently opened project or navigate to the launcher screen.
30
+ *
31
+ * - `'most-recent'` - Attempt to launch directly into a previously opened project and if unable to connect,
32
+ * fall back to the launcher screen.
33
+ *
34
+ * - `'launcher'` - Opens the launcher screen.
35
+ *
36
+ * @default 'most-recent'
37
+ */
38
+ launchMode?: 'most-recent' | 'launcher';
39
+ /**
40
+ * @deprecated use the `launchMode` property instead
41
+ */
42
+ launchModeExperimental?: 'most-recent' | 'launcher';
43
+ };
44
+
45
+ const schema: JSONSchemaType<PluginConfigType> = {
46
+ type: 'object',
47
+ properties: {
48
+ launchMode: {
49
+ type: 'string',
50
+ enum: ['most-recent', 'launcher'],
51
+ nullable: true,
52
+ },
53
+ launchModeExperimental: {
54
+ type: 'string',
55
+ enum: ['most-recent', 'launcher'],
56
+ nullable: true,
57
+ },
58
+ android: {
59
+ type: 'object',
60
+ properties: {
61
+ launchMode: {
62
+ type: 'string',
63
+ enum: ['most-recent', 'launcher'],
64
+ nullable: true,
65
+ },
66
+ launchModeExperimental: {
67
+ type: 'string',
68
+ enum: ['most-recent', 'launcher'],
69
+ nullable: true,
70
+ },
71
+ },
72
+ nullable: true,
73
+ },
74
+ ios: {
75
+ type: 'object',
76
+ properties: {
77
+ launchMode: {
78
+ type: 'string',
79
+ enum: ['most-recent', 'launcher'],
80
+ nullable: true,
81
+ },
82
+ launchModeExperimental: {
83
+ type: 'string',
84
+ enum: ['most-recent', 'launcher'],
85
+ nullable: true,
86
+ },
87
+ },
88
+ nullable: true,
89
+ },
90
+ },
91
+ };
92
+
93
+ /**
94
+ * @ignore
95
+ */
96
+ export function validateConfig<T>(config: T): PluginConfigType {
97
+ const validate = new Ajv({ allowUnionTypes: true }).compile(schema);
98
+ if (!validate(config)) {
99
+ throw new Error('Invalid expo-dev-launcher config: ' + JSON.stringify(validate.errors));
100
+ }
101
+
102
+ if (
103
+ config.launchModeExperimental ||
104
+ config.ios?.launchModeExperimental ||
105
+ config.android?.launchModeExperimental
106
+ ) {
107
+ warnOnce(
108
+ 'The `launchModeExperimental` property of expo-dev-launcher config plugin is deprecated and will be removed in a future SDK release. Use `launchMode` instead.'
109
+ );
110
+ }
111
+
112
+ return config;
113
+ }
114
+
115
+ const warnMap: Record<string, boolean> = {};
116
+ function warnOnce(message: string) {
117
+ if (!warnMap[message]) {
118
+ warnMap[message] = true;
119
+ console.warn(message);
120
+ }
121
+ }
@@ -0,0 +1,50 @@
1
+ import {
2
+ createRunOncePlugin,
3
+ AndroidConfig,
4
+ withInfoPlist,
5
+ withAndroidManifest,
6
+ } from 'expo/config-plugins';
7
+
8
+ import { PluginConfigType, validateConfig } from './pluginConfig';
9
+
10
+ const pkg = require('expo-dev-launcher/package.json');
11
+
12
+ export default createRunOncePlugin<PluginConfigType>(
13
+ (config, props = {}) => {
14
+ validateConfig(props);
15
+
16
+ const iOSLaunchMode =
17
+ props.ios?.launchMode ??
18
+ props.launchMode ??
19
+ props.ios?.launchModeExperimental ??
20
+ props.launchModeExperimental;
21
+ if (iOSLaunchMode === 'launcher') {
22
+ config = withInfoPlist(config, (config) => {
23
+ config.modResults['DEV_CLIENT_TRY_TO_LAUNCH_LAST_BUNDLE'] = false;
24
+ return config;
25
+ });
26
+ }
27
+
28
+ const androidLaunchMode =
29
+ props.android?.launchMode ??
30
+ props.launchMode ??
31
+ props.android?.launchModeExperimental ??
32
+ props.launchModeExperimental;
33
+ if (androidLaunchMode === 'launcher') {
34
+ config = withAndroidManifest(config, (config) => {
35
+ const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
36
+
37
+ AndroidConfig.Manifest.addMetaDataItemToMainApplication(
38
+ mainApplication,
39
+ 'DEV_CLIENT_TRY_TO_LAUNCH_LAST_BUNDLE',
40
+ false?.toString()
41
+ );
42
+ return config;
43
+ });
44
+ }
45
+
46
+ return config;
47
+ },
48
+ pkg.name,
49
+ pkg.version
50
+ );
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "expo-module-scripts/tsconfig.plugin",
3
+ "compilerOptions": {
4
+ "outDir": "build",
5
+ "rootDir": "src",
6
+ },
7
+ "include": ["./src"],
8
+ "exclude": ["**/__mocks__/*", "**/__tests__/*"],
9
+ }