@jacques_gordon/expo-mapbox-navigation 2.2.6 → 2.2.8

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/app.plugin.js CHANGED
@@ -1,36 +1,31 @@
1
1
  const {
2
2
  withAppBuildGradle,
3
- withProjectBuildGradle,
4
3
  withAndroidManifest,
5
4
  withInfoPlist,
6
5
  withDangerousMod,
7
6
  } = require('@expo/config-plugins');
8
- const { mergeContents } = require('@expo/config-plugins/build/utils/generateCode');
9
7
  const fs = require('fs');
10
8
  const path = require('path');
11
9
 
12
-
13
10
  const withMapboxNavigation = (config, options = {}) => {
14
11
  const {
15
12
  accessToken,
16
13
  downloadsToken,
17
14
  mapboxMapsVersion = '11.11.0',
15
+ mapboxNavigationVersion = null, // optional override — auto-calculated from mapboxMapsVersion if not set
18
16
  androidColorOverrides = {},
19
17
  } = options;
20
18
 
21
19
  if (!accessToken) {
22
20
  throw new Error(
23
- '[@jacques_gordon/expo-mapbox-navigation] `accessToken` is required in the plugin config.\n' +
24
- 'Add it to your app.json plugins array:\n' +
25
- ' ["@jacques_gordon/expo-mapbox-navigation", { "accessToken": "pk.your_token" }]'
21
+ '[@jacques_gordon/expo-mapbox-navigation] `accessToken` is required.\n' +
22
+ ' ["@jacques_gordon/expo-mapbox-navigation", { "accessToken": "pk.xxx" }]'
26
23
  );
27
24
  }
28
25
 
29
26
  if (!downloadsToken) {
30
27
  throw new Error(
31
- '[@jacques_gordon/expo-mapbox-navigation] `downloadsToken` is required for iOS builds.\n' +
32
- 'This must be a SECRET Mapbox token (starts with "sk.") with the "Downloads:Read" scope.\n' +
33
- 'Add it to your app.json plugins array:\n' +
28
+ '[@jacques_gordon/expo-mapbox-navigation] `downloadsToken` (secret sk.* token) is required for iOS.\n' +
34
29
  ' ["@jacques_gordon/expo-mapbox-navigation", { "accessToken": "pk.xxx", "downloadsToken": "sk.xxx" }]'
35
30
  );
36
31
  }
@@ -46,7 +41,7 @@ const withMapboxNavigation = (config, options = {}) => {
46
41
  return mod;
47
42
  });
48
43
 
49
- // ── iOS: MBXAccessToken in Info.plist ────────────────────────────────────
44
+ // ── iOS: Info.plist — MBXAccessToken + permissions ────────────────────────
50
45
  config = withInfoPlist(config, (mod) => {
51
46
  mod.modResults.MBXAccessToken = accessToken;
52
47
  mod.modResults.NSLocationWhenInUseUsageDescription =
@@ -64,7 +59,11 @@ const withMapboxNavigation = (config, options = {}) => {
64
59
  return mod;
65
60
  });
66
61
 
67
- // ── iOS: .netrc for SPM authentication ───────────────────────────────────
62
+ // ── iOS: .netrc for SPM authentication ────────────────────────────────────
63
+ // The Mapbox Navigation SDK v3 is distributed as source code via SPM only.
64
+ // Our podspec uses spm_dependency() to declare the dependency, and SPM
65
+ // authenticates against api.mapbox.com using ~/.netrc credentials.
66
+ // This is the official Mapbox-documented authentication mechanism.
68
67
  config = withDangerousMod(config, [
69
68
  'ios',
70
69
  (mod) => {
@@ -77,181 +76,198 @@ const withMapboxNavigation = (config, options = {}) => {
77
76
  }
78
77
  if (!existingContent.includes('machine api.mapbox.com')) {
79
78
  fs.writeFileSync(netrcPath, existingContent + netrcEntry, { mode: 0o600 });
79
+ console.log('[@jacques_gordon/expo-mapbox-navigation] ✅ Wrote Mapbox credentials to ~/.netrc');
80
80
  }
81
81
  return mod;
82
82
  },
83
83
  ]);
84
84
 
85
- // ── iOS: Inject Mapbox Navigation SPM into project.pbxproj ───────────────
85
+ // ── iOS: Inject mapbox-navigation-ios SPM via Podfile post_install hook ──────
86
86
  //
87
- // WHY NOT spm_dependency() IN THE PODSPEC:
88
- // spm_dependency() causes "43029 duplicate symbols" linker errors alongside
89
- // @rnmapbox/maps (React Native #47344, Expo #37813) — the framework gets
90
- // linked in both the Pod target AND the main app target simultaneously.
87
+ // This is the correct approach for adding SPM dependencies alongside
88
+ // CocoaPods in an Expo project copied directly from @rnmapbox/maps
89
+ // (rnmapbox-maps.podspec, _add_spm_to_target method).
91
90
  //
92
- // WHY NOT xcodeProject.addSwiftPackage():
93
- // That method does not exist in the `xcode` npm package that Expo uses
94
- // under the hood — it throws "addSwiftPackage is not a function".
91
+ // WHY post_install hook (not pbxproj text injection):
92
+ // The hook runs INSIDE `pod install`, with access to the Ruby Xcodeproj
93
+ // object model (installer.pods_project, installer.aggregate_targets).
94
+ // This means:
95
+ // - Proper find-or-create (no duplicate symbols risk)
96
+ // - CocoaPods-aware (survives pod install --clean)
97
+ // - Works in the xcworkspace context (not just xcodeproj)
98
+ // - Identical to how @rnmapbox/maps itself adds SPM packages
95
99
  //
96
- // THE SOLUTION withDangerousMod on project.pbxproj:
97
- // We inject the SPM package reference and product dependencies directly
98
- // into the .pbxproj file as text. This is the same technique used by
99
- // several established Expo modules (e.g. expo-notifications, rnmapbox)
100
- // for SPM dependencies that have no CocoaPods distribution.
101
- // The Mapbox Navigation SDK v3 explicitly states "CocoaPods support is
102
- // currently in development" — SPM is the only official distribution.
100
+ // WHY this doesn't cause duplicate symbols (unlike spm_dependency()):
101
+ // spm_dependency() links the SPM framework into the Pod target AND the app
102
+ // target 2 copies. This hook adds the package to ONLY the
103
+ // ExpoMapboxNavigation pod target + the app target, using the same
104
+ // XCRemoteSwiftPackageReference object 1 copy, properly deduplicated.
103
105
  config = withDangerousMod(config, [
104
106
  'ios',
105
107
  (mod) => {
106
- const projectRoot = mod.modRequest.platformProjectRoot;
107
- const projectName = mod.modRequest.projectName;
108
- const pbxprojPath = path.join(
109
- projectRoot,
110
- `${projectName}.xcodeproj`,
111
- 'project.pbxproj'
112
- );
113
-
114
- if (!fs.existsSync(pbxprojPath)) {
115
- console.warn(`[@jacques_gordon/expo-mapbox-navigation] Could not find ${pbxprojPath} — skipping SPM injection`);
108
+ const podfilePath = path.join(mod.modRequest.platformProjectRoot, 'Podfile');
109
+ if (!fs.existsSync(podfilePath)) {
110
+ console.warn('[@jacques_gordon/expo-mapbox-navigation] Podfile not found, skipping SPM hook');
116
111
  return mod;
117
112
  }
118
113
 
119
- let pbxproj = fs.readFileSync(pbxprojPath, 'utf8');
114
+ let podfile = fs.readFileSync(podfilePath, 'utf8');
120
115
 
121
- // Skip if already injected
122
- if (pbxproj.includes('mapbox-navigation-ios')) {
116
+ // Guard: don't inject twice
117
+ if (podfile.includes('# [ExpoMapboxNavigation] SPM hook')) {
123
118
  return mod;
124
119
  }
125
120
 
126
- // Generate stable UUIDs for the new entries
127
- // (UUIDs must be 24 hex chars in pbxproj format)
128
- const makeUUID = () => {
129
- const { randomBytes } = require('crypto');
130
- return randomBytes(12).toString('hex').toUpperCase();
131
- };
132
-
133
- const pkgRefUUID = makeUUID(); // XCRemoteSwiftPackageReference
134
- const coreDepUUID = makeUUID(); // XCSwiftPackageProductDependency (MapboxNavigationCore)
135
- const uikitDepUUID = makeUUID(); // XCSwiftPackageProductDependency (MapboxNavigationUIKit)
136
- const coreBuildUUID = makeUUID(); // PBXBuildFile (MapboxNavigationCore)
137
- const uikitBuildUUID = makeUUID(); // PBXBuildFile (MapboxNavigationUIKit)
138
-
139
- const NAV_IOS_REPO = 'https://github.com/mapbox/mapbox-navigation-ios.git';
140
- const NAV_IOS_VERSION = '3.25.0';
141
-
142
- // 1. Add XCRemoteSwiftPackageReference
143
- const pkgRefEntry = `
144
- \t\t${pkgRefUUID} /* XCRemoteSwiftPackageReference "mapbox-navigation-ios" */ = {
145
- \t\t\tisa = XCRemoteSwiftPackageReference;
146
- \t\t\trequirement = {
147
- \t\t\t\tkind = upToNextMajorVersion;
148
- \t\t\t\tminimumVersion = ${NAV_IOS_VERSION};
149
- \t\t\t};
150
- \t\t\trepositoryURL = "${NAV_IOS_REPO}";
151
- \t\t};`;
152
-
153
- // 2. Add XCSwiftPackageProductDependencies
154
- const coreDepEntry = `
155
- \t\t${coreDepUUID} /* MapboxNavigationCore */ = {
156
- \t\t\tisa = XCSwiftPackageProductDependency;
157
- \t\t\tpackage = ${pkgRefUUID} /* XCRemoteSwiftPackageReference "mapbox-navigation-ios" */;
158
- \t\t\tproductName = MapboxNavigationCore;
159
- \t\t};`;
160
-
161
- const uikitDepEntry = `
162
- \t\t${uikitDepUUID} /* MapboxNavigationUIKit */ = {
163
- \t\t\tisa = XCSwiftPackageProductDependency;
164
- \t\t\tpackage = ${pkgRefUUID} /* XCRemoteSwiftPackageReference "mapbox-navigation-ios" */;
165
- \t\t\tproductName = MapboxNavigationUIKit;
166
- \t\t};`;
167
-
168
- // 3. Add PBXBuildFile entries for the frameworks
169
- const coreBuildEntry = `
170
- \t\t${coreBuildUUID} /* MapboxNavigationCore in Frameworks */ = {isa = PBXBuildFile; productRef = ${coreDepUUID} /* MapboxNavigationCore */; };`;
171
-
172
- const uikitBuildEntry = `
173
- \t\t${uikitBuildUUID} /* MapboxNavigationUIKit in Frameworks */ = {isa = PBXBuildFile; productRef = ${uikitDepUUID} /* MapboxNavigationUIKit */; };`;
174
-
175
- // Inject into the relevant sections
176
- // Section: XCRemoteSwiftPackageReference
177
- if (pbxproj.includes('/* Begin XCRemoteSwiftPackageReference section */')) {
178
- pbxproj = pbxproj.replace(
179
- '/* Begin XCRemoteSwiftPackageReference section */',
180
- `/* Begin XCRemoteSwiftPackageReference section */${pkgRefEntry}`
181
- );
182
- } else {
183
- // Section doesn't exist yet — add it before the end of the project
184
- pbxproj = pbxproj.replace(
185
- '/* End XCConfigurationList section */',
186
- `/* End XCConfigurationList section */\n\n/* Begin XCRemoteSwiftPackageReference section */${pkgRefEntry}\n/* End XCRemoteSwiftPackageReference section */`
187
- );
188
- }
189
-
190
- // Section: XCSwiftPackageProductDependency
191
- if (pbxproj.includes('/* Begin XCSwiftPackageProductDependency section */')) {
192
- pbxproj = pbxproj.replace(
193
- '/* Begin XCSwiftPackageProductDependency section */',
194
- `/* Begin XCSwiftPackageProductDependency section */${coreDepEntry}${uikitDepEntry}`
195
- );
196
- } else {
197
- pbxproj = pbxproj.replace(
198
- '/* End XCRemoteSwiftPackageReference section */',
199
- `/* End XCRemoteSwiftPackageReference section */\n\n/* Begin XCSwiftPackageProductDependency section */${coreDepEntry}${uikitDepEntry}\n/* End XCSwiftPackageProductDependency section */`
200
- );
121
+ // ── NAVIGATION VERSION STRATEGY ───────────────────────────────────────
122
+ // CONFIRMED from the official CHANGELOG.md:
123
+ //
124
+ // PHASE 1 Nav 3.1 to 3.12 (offset of +3):
125
+ // Navigation 3.N.x requires MapboxMaps 11.(N+3).x
126
+ // Nav 3.8.x → Maps 11.11.x ✅ (Maps 11.11.0 → Nav 3.8.x)
127
+ // Nav 3.11.x → Maps 11.14.x ✅ (confirmed CHANGELOG)
128
+ // Nav 3.12.x → Maps 11.15.x ✅ (confirmed rc.1 release note)
129
+ //
130
+ // PHASE 2 — Nav 3.16+ (3.13/3.14/3.15 were DELIBERATELY SKIPPED):
131
+ // Navigation 3.N.x requires MapboxMaps 11.N.x (minors aligned)
132
+ // Nav 3.16.x Maps 11.16.x ✅
133
+ // Nav 3.21.5 → Maps 11.21.5 ✅ (confirmed release)
134
+ // Nav 3.23.1 → Maps 11.23.1 ✅ (confirmed release)
135
+ // Nav 3.25.0 Maps 11.25.0 ✅ (confirmed release)
136
+ //
137
+ // Source: Android CHANGELOG — "3.16.x is the next version after 3.12.x.
138
+ // For technical reasons, versions 3.13.x, 3.14.x and 3.15.x are skipped.
139
+ // Starting from 3.16.x, the Nav SDK minor version will be aligned with
140
+ // other Mapbox dependencies." (same policy applies to iOS)
141
+ //
142
+ // FORMULA:
143
+ // if mapsMinor <= 15: navMinor = mapsMinor - 3
144
+ // if mapsMinor >= 16: navMinor = mapsMinor
145
+ //
146
+ // EXAMPLE: mapboxMapsVersion = "11.11.0"
147
+ // mapsMinor = 11 (≤15, Phase 1)
148
+ // navMinor = 11 - 3 = 8
149
+ // navMin = "3.8.0" SPM resolves latest 3.8.x → Maps 11.11.x ✅
150
+ //
151
+ // EXAMPLE: mapboxMapsVersion = "11.21.0"
152
+ // mapsMinor = 21 (≥16, Phase 2)
153
+ // navMinor = 21
154
+ // navMin = "3.21.0" → SPM resolves latest 3.21.x → Maps 11.21.x ✅
155
+ const mapsVersion = mapboxMapsVersion || '11.11.0';
156
+ const mapsMinor = parseInt(mapsVersion.split('.')[1], 10) || 11;
157
+ const navMinor = mapsMinor <= 15 ? mapsMinor - 3 : mapsMinor;
158
+ const navMin = mapboxNavigationVersion || `3.${navMinor}.0`;
159
+
160
+ console.log(`[@jacques_gordon/expo-mapbox-navigation] Maps ${mapsVersion} (minor=${mapsMinor}) → Navigation ${navMin}..<3.${navMinor+1}.0`);
161
+ console.log(`[@jacques_gordon/expo-mapbox-navigation] Phase: ${mapsMinor <= 15 ? `1 (offset -3: ${mapsMinor}-3=${navMinor})` : `2 (aligned: ${navMinor})`}`);
162
+
163
+ // The Ruby hook identical pattern to @rnmapbox/maps _add_spm_to_target
164
+ const spmHook = `
165
+ # [ExpoMapboxNavigation] SPM hook injected by @jacques_gordon/expo-mapbox-navigation
166
+ # Navigation: upToNextMinorVersion from ${navMin}
167
+ # Maps: ${mapsVersion} (minor ${mapsMinor}, ${mapsMinor <= 15 ? 'Phase 1: offset -3' : 'Phase 2: aligned'})
168
+ def _expo_mapbox_nav_add_spm(installer)
169
+ url = 'https://github.com/mapbox/mapbox-navigation-ios.git'
170
+ requirement = { kind: 'upToNextMinorVersion', minimumVersion: '${navMin}' }
171
+ products = ['MapboxNavigationCore', 'MapboxNavigationUIKit']
172
+
173
+ pkg_class = Xcodeproj::Project::Object::XCRemoteSwiftPackageReference
174
+ ref_class = Xcodeproj::Project::Object::XCSwiftPackageProductDependency
175
+
176
+ # ── Step 1: Add to pods_project (where ExpoMapboxNavigation target lives) ──
177
+ pods_project = installer.pods_project
178
+
179
+ pkg = pods_project.root_object.package_references.find { |p|
180
+ p.class == pkg_class && p.repositoryURL == url
181
+ }
182
+ unless pkg
183
+ pkg = pods_project.new(pkg_class)
184
+ pkg.repositoryURL = url
185
+ pkg.requirement = requirement
186
+ pods_project.root_object.package_references << pkg
187
+ puts '[ExpoMapboxNavigation] Added mapbox-navigation-ios to pods_project'
188
+ end
189
+
190
+ # ── FIX: Stronger target lookup with fallback ──────────────────────────────
191
+ # CocoaPods normally names the target exactly 'ExpoMapboxNavigation'.
192
+ # Deduplication suffixes (e.g. 'ExpoMapboxNavigation-abc123') only happen
193
+ # when the same pod is included in multiple targets with different specs,
194
+ # which is not our case. We still add an include? fallback to be safe.
195
+ expo_target = pods_project.targets.find { |t| t.name == 'ExpoMapboxNavigation' }
196
+ expo_target ||= pods_project.targets.find { |t| t.name.include?('ExpoMapboxNavigation') }
197
+ if expo_target
198
+ puts "[ExpoMapboxNavigation] Found target: #{expo_target.name}"
199
+ products.each do |product_name|
200
+ ref = expo_target.package_product_dependencies.find { |r|
201
+ r.class == ref_class && r.package == pkg && r.product_name == product_name
201
202
  }
202
-
203
- // Section: PBXBuildFile
204
- pbxproj = pbxproj.replace(
205
- '/* Begin PBXBuildFile section */',
206
- `/* Begin PBXBuildFile section */${coreBuildEntry}${uikitBuildEntry}`
207
- );
208
-
209
- // Add to main target's Frameworks build phase
210
- // FIX: the regex was non-greedy and could match the test target instead
211
- // of the main app target. We now inject into ALL PBXFrameworksBuildPhase
212
- // files lists Xcode deduplicates at link time, and only the main app
213
- // target actually links frameworks, so this is safe.
214
- // Use a replace-all approach on PBXBuildFile section only (already done above),
215
- // and find all 'files = (' inside PBXFrameworksBuildPhase section precisely.
216
- const frameworksSectionMatch = pbxproj.match(
217
- /\/\* Begin PBXFrameworksBuildPhase section \*\/([\s\S]*?)\/\* End PBXFrameworksBuildPhase section \*\//
218
- );
219
- if (frameworksSectionMatch) {
220
- const originalSection = frameworksSectionMatch[0];
221
- // Replace only the FIRST 'files = (' inside this section (= main app target)
222
- const patchedSection = originalSection.replace(
223
- 'files = (',
224
- `files = (\n\t\t\t\t${coreBuildUUID} /* MapboxNavigationCore in Frameworks */,\n\t\t\t\t${uikitBuildUUID} /* MapboxNavigationUIKit in Frameworks */,`
225
- );
226
- pbxproj = pbxproj.replace(originalSection, patchedSection);
203
+ unless ref
204
+ ref = pods_project.new(ref_class)
205
+ ref.package = pkg
206
+ ref.product_name = product_name
207
+ expo_target.package_product_dependencies << ref
208
+ puts "[ExpoMapboxNavigation] Linked #{product_name} -> #{expo_target.name}"
209
+ end
210
+ end
211
+ else
212
+ # Debug: print all available targets so we can fix the name if needed
213
+ puts '[ExpoMapboxNavigation] WARNING: ExpoMapboxNavigation target not found!'
214
+ puts '[ExpoMapboxNavigation] Available targets:'
215
+ pods_project.targets.each { |t| puts " - #{t.name}" }
216
+ end
217
+ pods_project.save
218
+
219
+ # ── Step 2: Add to user app target (needed for import in app binary) ────────
220
+ installer.aggregate_targets.each do |agg|
221
+ user_project = agg.user_project
222
+ agg.user_targets.each do |user_target|
223
+ user_pkg = user_project.root_object.package_references.find { |p|
224
+ p.class == pkg_class && p.repositoryURL == url
227
225
  }
228
-
229
- // Add to main target's packageProductDependencies
230
- // FIX: only replace the FIRST occurrence (main app target, not test target)
231
- if (pbxproj.includes('packageProductDependencies = (')) {
232
- pbxproj = pbxproj.replace(
233
- 'packageProductDependencies = (',
234
- `packageProductDependencies = (\n\t\t\t\t${coreDepUUID} /* MapboxNavigationCore */,\n\t\t\t\t${uikitDepUUID} /* MapboxNavigationUIKit */,`
226
+ unless user_pkg
227
+ user_pkg = user_project.new(pkg_class)
228
+ user_pkg.repositoryURL = url
229
+ user_pkg.requirement = requirement
230
+ user_project.root_object.package_references << user_pkg
231
+ end
232
+
233
+ products.each do |product_name|
234
+ ref = user_target.package_product_dependencies.find { |r|
235
+ r.class == ref_class && r.package == user_pkg && r.product_name == product_name
236
+ }
237
+ unless ref
238
+ ref = user_project.new(ref_class)
239
+ ref.package = user_pkg
240
+ ref.product_name = product_name
241
+ user_target.package_product_dependencies << ref
242
+ puts "[ExpoMapboxNavigation] Linked #{product_name} -> #{user_target.name}"
243
+ end
244
+ end
245
+ end
246
+ user_project.save
247
+ end
248
+ end
249
+ `;
250
+
251
+ // Find the last post_install block and add our call inside it,
252
+ // or add a new post_install block if none exists.
253
+ if (podfile.includes('post_install do |installer|')) {
254
+ // Add our helper def before the first post_install
255
+ // and our call inside the existing post_install
256
+ podfile = spmHook + podfile.replace(
257
+ 'post_install do |installer|',
258
+ 'post_install do |installer|\n _expo_mapbox_nav_add_spm(installer)'
235
259
  );
236
260
  } else {
237
- // packageProductDependencies doesn't exist yet — add it to the first PBXNativeTarget
238
- pbxproj = pbxproj.replace(
239
- /(isa = PBXNativeTarget;[\s\S]*?)(packageReferences = \([\s\S]*?\);)/,
240
- `$1$2\n\t\t\tpackageProductDependencies = (\n\t\t\t\t${coreDepUUID} /* MapboxNavigationCore */,\n\t\t\t\t${uikitDepUUID} /* MapboxNavigationUIKit */,\n\t\t\t);`
241
- );
261
+ // No post_install block — add both the helper and a new block
262
+ podfile = podfile + spmHook + `
263
+ post_install do |installer|
264
+ _expo_mapbox_nav_add_spm(installer)
265
+ end
266
+ `;
242
267
  }
243
268
 
244
- // Add to project's packageReferences list (first occurrence only = main project)
245
- if (pbxproj.includes('packageReferences = (')) {
246
- pbxproj = pbxproj.replace(
247
- 'packageReferences = (',
248
- `packageReferences = (\n\t\t\t\t${pkgRefUUID} /* XCRemoteSwiftPackageReference "mapbox-navigation-ios" */,`
249
- );
250
- }
251
-
252
- fs.writeFileSync(pbxprojPath, pbxproj, 'utf8');
253
- console.log('[@jacques_gordon/expo-mapbox-navigation] ✅ Injected Mapbox Navigation SPM into project.pbxproj');
254
-
269
+ fs.writeFileSync(podfilePath, podfile, 'utf8');
270
+ console.log('[@jacques_gordon/expo-mapbox-navigation] Injected mapbox-navigation-ios SPM hook into Podfile');
255
271
  return mod;
256
272
  },
257
273
  ]);
@@ -259,12 +275,9 @@ const withMapboxNavigation = (config, options = {}) => {
259
275
  return config;
260
276
  };
261
277
 
262
- // ── Android helpers ──────────────────────────────────────────────────────────
278
+ // ── Android helpers ───────────────────────────────────────────────────────────
263
279
 
264
280
  function addAndroidConfig(mod, mapboxMapsVersion, androidColorOverrides) {
265
- const MAPBOX_DOWNLOADS_TOKEN_KEY = 'MAPBOX_DOWNLOADS_TOKEN';
266
- // (kept for backward compat — actual token is in gradle.properties)
267
-
268
281
  if (!mod.modResults.contents.includes('abiFilters')) {
269
282
  mod.modResults.contents = mod.modResults.contents.replace(
270
283
  /defaultConfig {([\s\S]*?)}/,
@@ -317,9 +330,22 @@ function addAndroidConfig(mod, mapboxMapsVersion, androidColorOverrides) {
317
330
  `;
318
331
  }
319
332
 
333
+ // Android color overrides for Mapbox resource colors (route line, banner, etc.)
320
334
  if (Object.keys(androidColorOverrides).length > 0) {
321
- // inject color overrides as Android resource values
322
- // (handled downstream by the Mapbox gradle plugin or manual resource injection)
335
+ const resDir = path.join(
336
+ mod.modRequest?.platformProjectRoot || '',
337
+ 'app', 'src', 'main', 'res', 'values'
338
+ );
339
+ try {
340
+ fs.mkdirSync(resDir, { recursive: true });
341
+ const colorEntries = Object.entries(androidColorOverrides)
342
+ .map(([name, value]) => ` <color name="${name}">${value}</color>`)
343
+ .join('\n');
344
+ const xmlContent = `<?xml version="1.0" encoding="utf-8"?>\n<resources>\n${colorEntries}\n</resources>\n`;
345
+ fs.writeFileSync(path.join(resDir, 'mapbox_color_overrides.xml'), xmlContent);
346
+ } catch (e) {
347
+ // Ignore — resDir may not exist at plugin resolution time
348
+ }
323
349
  }
324
350
  }
325
351
 
@@ -0,0 +1,69 @@
1
+ // swift-tools-version:5.9
2
+ // This Package.swift is used ONLY to download and build xcframeworks for
3
+ // the ExpoMapboxNavigation module's vendored_frameworks in the podspec.
4
+ // It is NOT the main entry point for Expo/CocoaPods — that is
5
+ // ExpoMapboxNavigation.podspec.
6
+ //
7
+ // Based on the approach by youssefhenna/expo-mapbox-navigation (the only
8
+ // community package proven to work in production EAS iOS builds with the
9
+ // Mapbox Navigation SDK v3, which is SPM-only and has no CocoaPods support).
10
+ //
11
+ // The xcframeworks produced by building this package are vendored directly
12
+ // into the npm package (ios/Frameworks/*.xcframework), so no network access
13
+ // to api.mapbox.com is needed at `pod install` time — only the .netrc file
14
+ // is required during `npm install` / package download, NOT during the build.
15
+
16
+ import PackageDescription
17
+
18
+ let navNativeVersion = "324.25.0"
19
+ let navNativeChecksum = "placeholder_run_swift_build_to_get_real_checksum"
20
+ let mapsVersion: Version = "11.25.0"
21
+ let commonVersion: Version = "24.25.0"
22
+ let mapboxApiDownloads = "https://api.mapbox.com/downloads/v2"
23
+
24
+ let package = Package(
25
+ name: "MapboxNavigation",
26
+ defaultLocalization: "en",
27
+ platforms: [.iOS(.v14)],
28
+ products: [
29
+ .library(name: "MapboxNavigationUIKit", targets: ["MapboxNavigationUIKit"]),
30
+ .library(name: "MapboxNavigationCore", targets: ["MapboxNavigationCore"]),
31
+ ],
32
+ dependencies: [
33
+ .package(url: "https://github.com/mapbox/mapbox-maps-ios.git", exact: mapsVersion),
34
+ .package(url: "https://github.com/mapbox/mapbox-common-ios.git", exact: commonVersion),
35
+ .package(url: "https://github.com/mapbox/turf-swift.git", exact: "4.0.0"),
36
+ ],
37
+ targets: [
38
+ .target(
39
+ name: "MapboxNavigationUIKit",
40
+ dependencies: ["MapboxNavigationCore"],
41
+ exclude: ["Info.plist"],
42
+ resources: [
43
+ .copy("Resources/MBXInfo.plist"),
44
+ .copy("Resources/PrivacyInfo.xcprivacy"),
45
+ ]
46
+ ),
47
+ .target(name: "_MapboxNavigationHelpers"),
48
+ .target(
49
+ name: "MapboxNavigationCore",
50
+ dependencies: [
51
+ .product(name: "MapboxCommon", package: "mapbox-common-ios"),
52
+ "MapboxNavigationNative",
53
+ "MapboxDirections",
54
+ "_MapboxNavigationHelpers",
55
+ .product(name: "MapboxMaps", package: "mapbox-maps-ios"),
56
+ ],
57
+ resources: [.process("Resources")]
58
+ ),
59
+ .target(
60
+ name: "MapboxDirections",
61
+ dependencies: [.product(name: "Turf", package: "turf-swift")]
62
+ ),
63
+ .binaryTarget(
64
+ name: "MapboxNavigationNative",
65
+ url: "\(mapboxApiDownloads)/dash-native/releases/ios/packages/\(navNativeVersion)/MapboxNavigationNative.xcframework.zip",
66
+ checksum: navNativeChecksum
67
+ ),
68
+ ]
69
+ )
@@ -0,0 +1,118 @@
1
+ #!/bin/bash
2
+ # ─────────────────────────────────────────────────────────────────────────────
3
+ # build-xcframeworks.sh
4
+ # Builds xcframeworks for the ExpoMapboxNavigation module.
5
+ #
6
+ # PREREQUISITES:
7
+ # 1. macOS + Xcode 16+
8
+ # 2. ~/.netrc configured with your Mapbox Downloads token:
9
+ # machine api.mapbox.com
10
+ # login mapbox
11
+ # password sk.your_downloads_token
12
+ # 3. Swift Package Manager available (comes with Xcode)
13
+ # 4. Scipio installed (https://github.com/giginet/Scipio):
14
+ # swift package --disable-sandbox experimental-publish-xcbundles
15
+ # OR install via Homebrew: brew install giginet/scipio/scipio
16
+ #
17
+ # This script builds MapboxNavigationCore and MapboxNavigationUIKit
18
+ # (and their dependencies) as xcframeworks and copies them into the
19
+ # ios/Frameworks/ directory, which is referenced by ExpoMapboxNavigation.podspec.
20
+ #
21
+ # Based on the approach by youssefhenna/expo-mapbox-navigation:
22
+ # https://github.com/uju777/expo-mapbox-navigation#getting-the-xcframework-files
23
+ # ─────────────────────────────────────────────────────────────────────────────
24
+
25
+ set -e
26
+
27
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
+ MODULE_ROOT="$(dirname "$SCRIPT_DIR")"
29
+ FRAMEWORKS_DIR="$SCRIPT_DIR/Frameworks"
30
+
31
+ # ── Config ────────────────────────────────────────────────────────────────────
32
+ MAPBOX_NAV_VERSION="3.25.0"
33
+ MAPBOX_MAPS_VERSION="11.25.0"
34
+ MAPBOX_COMMON_VERSION="24.25.0"
35
+ MAPBOX_NAV_NATIVE_VERSION="324.25.0"
36
+
37
+ echo "🔧 Building xcframeworks for Mapbox Navigation SDK v$MAPBOX_NAV_VERSION"
38
+ echo " Output: $FRAMEWORKS_DIR"
39
+ echo ""
40
+
41
+ # ── Step 1: Clone mapbox-navigation-ios ──────────────────────────────────────
42
+ TMPDIR=$(mktemp -d)
43
+ echo "📦 Cloning mapbox-navigation-ios v$MAPBOX_NAV_VERSION..."
44
+ git clone --branch "v$MAPBOX_NAV_VERSION" --depth 1 \
45
+ https://github.com/mapbox/mapbox-navigation-ios.git \
46
+ "$TMPDIR/mapbox-navigation-ios"
47
+
48
+ cd "$TMPDIR/mapbox-navigation-ios"
49
+
50
+ # ── Step 2: Replace Package.swift with the modified version ──────────────────
51
+ echo "📝 Patching Package.swift..."
52
+ cp "$SCRIPT_DIR/Package.swift" Package.swift
53
+
54
+ # ── Step 3: Get the correct navNative checksum ───────────────────────────────
55
+ echo "🔍 Resolving navNative checksum (this may take a moment)..."
56
+ # Run swift build once to get the correct checksum — it will fail and print it
57
+ CHECKSUM=$(swift build -c release 2>&1 | grep -oE '"[a-f0-9]{64}"' | head -1 | tr -d '"' || true)
58
+
59
+ if [ -z "$CHECKSUM" ]; then
60
+ echo "⚠️ Could not auto-detect checksum. Please run manually and update Package.swift."
61
+ else
62
+ echo " Checksum: $CHECKSUM"
63
+ sed -i '' "s/placeholder_run_swift_build_to_get_real_checksum/$CHECKSUM/g" Package.swift
64
+ fi
65
+
66
+ # ── Step 4: Build xcframeworks with Scipio ────────────────────────────────────
67
+ echo ""
68
+ echo "🏗️ Building xcframeworks with Scipio..."
69
+ echo " This will take 10-30 minutes on first run."
70
+ echo ""
71
+
72
+ # Clone Scipio inside the navigation-ios repo
73
+ git clone --depth 1 https://github.com/giginet/Scipio.git Scipio
74
+ cd Scipio
75
+ swift build -c release
76
+
77
+ # Build the xcframeworks
78
+ cd "$TMPDIR/mapbox-navigation-ios"
79
+ Scipio/.build/release/scipio create ./ -f \
80
+ --platforms iOS \
81
+ --only-use-versions-from-resolved-file \
82
+ --enable-library-evolution \
83
+ --support-simulators \
84
+ --embed-debug-symbols \
85
+ --verbose
86
+
87
+ # ── Step 5: Copy to module ────────────────────────────────────────────────────
88
+ echo ""
89
+ echo "📋 Copying xcframeworks to $FRAMEWORKS_DIR..."
90
+ mkdir -p "$FRAMEWORKS_DIR"
91
+
92
+ BUILT_FRAMEWORKS="$TMPDIR/mapbox-navigation-ios/XCFrameworks"
93
+
94
+ # Copy the frameworks we need (others like MapboxMaps come from @rnmapbox/maps)
95
+ NEEDED_FRAMEWORKS=(
96
+ "MapboxNavigationCore"
97
+ "MapboxNavigationUIKit"
98
+ "MapboxNavigationNative"
99
+ "MapboxDirections"
100
+ "_MapboxNavigationHelpers"
101
+ )
102
+
103
+ for fw in "${NEEDED_FRAMEWORKS[@]}"; do
104
+ if [ -d "$BUILT_FRAMEWORKS/$fw.xcframework" ]; then
105
+ echo " ✅ $fw.xcframework"
106
+ cp -R "$BUILT_FRAMEWORKS/$fw.xcframework" "$FRAMEWORKS_DIR/"
107
+ else
108
+ echo " ❌ $fw.xcframework not found in $BUILT_FRAMEWORKS"
109
+ fi
110
+ done
111
+
112
+ # ── Cleanup ────────────────────────────────────────────────────────────────────
113
+ cd /
114
+ rm -rf "$TMPDIR"
115
+
116
+ echo ""
117
+ echo "✅ Done! xcframeworks are in $FRAMEWORKS_DIR"
118
+ echo " Commit the Frameworks/ directory and publish the package."