Skip to content

feat: add Expo config plugin for SDK 56+#22

Merged
jkasprzyk17 merged 3 commits into
mainfrom
feat/expo-config-plugin
Jun 28, 2026
Merged

feat: add Expo config plugin for SDK 56+#22
jkasprzyk17 merged 3 commits into
mainfrom
feat/expo-config-plugin

Conversation

@jkasprzyk17

Copy link
Copy Markdown
Contributor

Summary

Ships a first-party Expo config plugin so consumers can configure react-native-nitro-maps from app.json / app.config.js and get the required native keys/permissions after expo prebuild — no manual edits. Closes #12.

export default {
  expo: {
    plugins: [
      ['react-native-nitro-maps', {
        googleMapsApiKey: process.env.GOOGLE_MAPS_API_KEY,
        locationPermission: 'Allow $(PRODUCT_NAME) to use your location for map features.',
      }],
    ],
  },
};

What's included

  • Plugin source (package/plugin/src/): withNitroMaps, android.ts, ios.ts, types.ts, index.ts using createRunOncePlugin.
  • Android mod: injects com.google.android.geo.API_KEY meta-data when googleMapsApiKey is set; adds ACCESS_FINE_LOCATION/ACCESS_COARSE_LOCATION (and ACCESS_BACKGROUND_LOCATION for always) from location options.
  • iOS mod: sets NSLocationWhenInUseUsageDescription / NSLocationAlwaysAndWhenInUseUsageDescription; googleMapsApiKey is a documented no-op stub for future iOS Google Maps (Add Google Maps provider on iOS #2).
  • Options (typed & exported): googleMapsApiKey, locationPermission, locationAlwaysPermission.
  • Packaging: app.plugin.js, package.json expo.plugin + build:plugin script, @expo/config-plugins devDependency, tsconfig.plugin.json.
  • Example app migrated to the plugin (manual Google Maps block removed); prebuild output reflected in example/ native projects.
  • Docs: README section + docs/expo-setup.md (SDK 56 walkthrough, EAS secrets pattern).
  • Tests: jest + ts-jest config; unit tests for Android/iOS mod functions.
  • CI: added a test step.

Test plan

  • bun run test — 13/13 passing
  • bun run typecheck — clean
  • bun run build:plugin — emits plugin/build/; app.plugin.js loads and returns a function
  • expo prebuild --clean on the example app with Expo SDK 56 + New Architecture
  • Verify Android manifest has API key meta-data and iOS Info.plist has the location string
  • Confirm prebuild does not fail when googleMapsApiKey is omitted

Made with Cursor

Add a first-party Expo config plugin so consumers can configure
react-native-nitro-maps via app.json / app.config.js without manual
native edits. Closes #12.

- Android mod: inject com.google.android.geo.API_KEY meta-data and
  location permissions (fine/coarse, background)
- iOS mod: set NSLocationWhenInUseUsageDescription /
  NSLocationAlwaysAndWhenInUseUsageDescription; stub Google Maps key (#2)
- Export NitroMapsPluginOptions types; use createRunOncePlugin
- Wire app.plugin.js, build:plugin script, jest + ts-jest config
- Migrate example app to the plugin (remove manual Google Maps block)
- Document setup in README and docs/expo-setup.md
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added/expanded an Expo config plugin (Expo SDK 56+/RN 0.78+) to automate Android/iOS Google Maps setup, including Google Maps API keys and location permission prompts.
  • Bug Fixes
    • Plugin logic now applies native API key and location settings only when the corresponding options are provided, with correct handling of “omit/false” and permission combinations.
  • Documentation
    • Added a dedicated Expo setup guide and updated the README with Expo config examples and troubleshooting; updated the example app configuration/permissions text.
  • Tests
    • Added plugin-focused Jest coverage and enabled CI to run the plugin test suite.

Walkthrough

Adds an Expo config plugin for Nitro Maps, with Android and iOS config mods, package build/test wiring, example app updates, and setup documentation.

Changes

Expo config plugin

Layer / File(s) Summary
Plugin options and entrypoints
package/plugin/src/types.ts, package/plugin/src/index.ts, package/plugin/src/withNitroMaps.ts, package/app.plugin.js
NitroMapsPluginOptions, type guards, the run-once plugin export, and the package root plugin shim are added.
Android config and tests
package/plugin/src/android.ts, package/plugin/src/android.test.ts
Android API-key injection, location-permission selection, plugin wrapping, and tests for both helpers are added.
iOS config and tests
package/plugin/src/ios.ts, package/plugin/src/ios.test.ts
iOS API-key handling, location usage strings, and the Info.plist wrapper are added with tests for the permission combinations.
Package build and CI wiring
package/package.json, package/tsconfig.plugin.json, package/jest.config.js, eslint.config.mjs, .gitignore, .github/workflows/ci.yml
Package publish files, plugin build and test scripts, TypeScript/Jest config, lint ignores, and the CI test step are updated.
Example app and docs
example/app.config.js, example/android/app/src/main/AndroidManifest.xml, example/ios/NitroMapsExample/Info.plist, README.md, docs/expo-setup.md, package/android/README.md
The example app switches to the plugin, native sample manifests add the required permissions, and the README and Expo setup docs describe the new flow.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, uses the required feat prefix, and accurately summarizes the Expo config plugin addition.
Description check ✅ Passed The description clearly matches the PR and describes the Expo plugin, docs, tests, packaging, and example migration.
Linked Issues check ✅ Passed The changes implement the Expo config plugin, platform mods, typed options, packaging, docs, tests, and example migration required by #12.
Out of Scope Changes check ✅ Passed No clear out-of-scope code changes stand out; the extra workflow, lint, and ignore updates support the plugin work.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • JIRA integration encountered authorization issues. Please disconnect and reconnect the integration in the CodeRabbit UI.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/gmi-software/react-native-nitro-maps/issues/comments/4811007504","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- review_stack_entry_start -->\n\n[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/gmi-software/react-native-nitro-maps/pull/22?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)\n\n<!-- review_stack_entry_end -->\n<!-- This is an auto-generated comment: review in progress by coderabbit.ai -->\n\n> [!NOTE]\n> Currently processing new changes in this PR. This may take a few minutes, please wait...\n> \n> <details>\n> <summary>⚙️ Run configuration</summary>\n> \n> **Configuration used**: Organization UI\n> \n> **Review profile**: CHILL\n> \n> **Plan**: Pro\n> \n> **Run ID**: `8043751b-78a9-431b-b236-32796b03bfeb`\n> \n> </details>\n> \n> <details>\n> <summary>📥 Commits</summary>\n> \n> Reviewing files that changed from the base of the PR and between 5d60983cbd05542fb1e4e065120fcea7c87b81d2 and 196c72accaac091f72bcd1c766ad47c7885a0656.\n> \n> </details>\n> \n> <details>\n> <summary>⛔ Files ignored due to path filters (1)</summary>\n> \n> * `bun.lock` is excluded by `!**/*.lock`\n> \n> </details>\n> \n> <details>\n> <summary>📒 Files selected for processing (20)</summary>\n> \n> * `.github/workflows/ci.yml`\n> * `.gitignore`\n> * `README.md`\n> * `docs/expo-setup.md`\n> * `eslint.config.mjs`\n> * `example/android/app/src/main/AndroidManifest.xml`\n> * `example/app.config.js`\n> * `example/ios/NitroMapsExample/Info.plist`\n> * `package/android/README.md`\n> * `package/app.plugin.js`\n> * `package/jest.config.js`\n> * `package/package.json`\n> * `package/plugin/src/android.test.ts`\n> * `package/plugin/src/android.ts`\n> * `package/plugin/src/index.ts`\n> * `package/plugin/src/ios.test.ts`\n> * `package/plugin/src/ios.ts`\n> * `package/plugin/src/types.ts`\n> * `package/plugin/src/withNitroMaps.ts`\n> * `package/tsconfig.plugin.json`\n> \n> </details>\n> \n> ```ascii\n>  _________________________________\n> < Pvt. Rabbit reporting for duty! >\n>  ---------------------------------\n>   \\\n>    \\   \\\n>         \\ /\\\n>         ( )\n>       .( o ).\n> ```\n\n<!-- end of auto-generated comment: review in progress by coderabbit.ai -->\n\n<!-- tips_start -->\n\n---\n\n\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands.</sub>\n\n<!-- tips_end -->"},"request":{"retryCount":3,"signal":{},"retries":3,"retryAfter":16}}}

@jkasprzyk17

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/expo-setup.md`:
- Around line 56-58: The `.env` example uses a naked fenced code block, which
triggers markdownlint MD040. Update the fenced block in the Expo setup doc to
include an explicit language hint such as dotenv or text so the example is
lint-clean and consistently highlighted; this is the fenced block containing
GOOGLE_MAPS_API_KEY in the markdown snippet.

In `@package/package.json`:
- Around line 32-34: The build:plugin step is leaving stale files in
plugin/build because tsc does not clean old artifacts before compiling. Update
the package.json scripts so the build:plugin workflow explicitly removes the
existing plugin/build output before running tsc, and make sure prepack still
goes through the cleaned build path. Use the build, build:plugin, and prepack
script entries as the place to apply the fix.
- Around line 34-40: The Expo plugin entry point currently points to a built
artifact that may not exist in a fresh checkout. Update the plugin entry wiring
in package.json and the app.plugin.js entry so Expo resolves the source plugin
directly, or ensure the plugin build is generated before Expo tries to load it.
Use the app.plugin.js and plugin/build/index symbols to locate the startup path
and remove the dependency on a missing prebuilt output.

In `@package/plugin/src/android.ts`:
- Around line 40-49: The Android permission builder in the location permissions
logic is allowing ACCESS_BACKGROUND_LOCATION to be added without any foreground
location permissions, which creates an invalid standalone always-location
config. Update the permission handling in the android.ts flow around
wantsWhenInUseLocation and wantsAlwaysLocation so that locationAlwaysPermission
either also includes the fine/coarse permissions or is rejected unless
foreground location is enabled. Keep the fix localized to the permissions.push
logic used by these helpers.

In `@package/plugin/src/ios.ts`:
- Around line 15-22: The iOS Info.plist setup in the plugin currently writes
only the always-location key when `wantsAlwaysLocation(options)` is true, which
leaves the when-in-use usage string missing for the always flow. Update the
logic in the `wantsAlwaysLocation` branch to ensure
`NSLocationWhenInUseUsageDescription` is always populated as the base
requirement, either by requiring `locationPermission` whenever
`locationAlwaysPermission` is set or by backfilling it from
`locationAlwaysPermission` when absent. Keep the change localized to the
`infoPlist` assignment block in `ios.ts`.

In `@README.md`:
- Around line 52-54: The README section has two separate blockquote paragraphs
separated by a blank line, which triggers markdownlint
MD028/no-blanks-blockquote. Update the existing blockquote content in README.md
so the Google Maps API key and EAS Secrets notes are either merged into a single
contiguous blockquote or the blank line between them is removed; use the
surrounding README blockquote text as the locator.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1e0d603a-b89f-4a60-a4ac-960f52033f14

📥 Commits

Reviewing files that changed from the base of the PR and between 5d60983 and 196c72a.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • .github/workflows/ci.yml
  • .gitignore
  • README.md
  • docs/expo-setup.md
  • eslint.config.mjs
  • example/android/app/src/main/AndroidManifest.xml
  • example/app.config.js
  • example/ios/NitroMapsExample/Info.plist
  • package/android/README.md
  • package/app.plugin.js
  • package/jest.config.js
  • package/package.json
  • package/plugin/src/android.test.ts
  • package/plugin/src/android.ts
  • package/plugin/src/index.ts
  • package/plugin/src/ios.test.ts
  • package/plugin/src/ios.ts
  • package/plugin/src/types.ts
  • package/plugin/src/withNitroMaps.ts
  • package/tsconfig.plugin.json

Comment thread docs/expo-setup.md Outdated
Comment thread package/package.json
Comment thread package/package.json
Comment thread package/plugin/src/android.ts Outdated
Comment thread package/plugin/src/ios.ts Outdated
Comment thread README.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
package/plugin/src/android.test.ts (1)

27-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the omitted-key regression test.

This only proves the happy path. The plugin’s contract also says googleMapsApiKey can be omitted without breaking prebuild, so the falsy branch in applyGoogleMapsApiKey needs an explicit test before someone breaks it later.

Proposed test
 describe('applyGoogleMapsApiKey', () => {
   it('injects com.google.android.geo.API_KEY meta-data into AndroidManifest', () => {
     const manifest = createEmptyManifest();
     applyGoogleMapsApiKey(manifest, 'test-api-key');

     expect(
       AndroidConfig.Manifest.getMainApplicationOrThrow(manifest)['meta-data'],
     ).toEqual([
       {
         $: {
           'android:name': GOOGLE_MAPS_API_KEY_META,
           'android:value': 'test-api-key',
         },
       },
     ]);
   });
+
+  it('does nothing when the API key is omitted', () => {
+    const manifest = createEmptyManifest();
+
+    expect(applyGoogleMapsApiKey(manifest, undefined)).toBe(manifest);
+    expect(
+      AndroidConfig.Manifest.getMainApplicationOrThrow(manifest)['meta-data'],
+    ).toBeUndefined();
+  });
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package/plugin/src/android.test.ts` around lines 27 - 40, Add a regression
test around applyGoogleMapsApiKey in android.test.ts for the omitted
googleMapsApiKey case, since the current test only covers the happy path. Create
a manifest with createEmptyManifest(), call applyGoogleMapsApiKey with an
undefined or otherwise falsy key, and assert that
AndroidConfig.Manifest.getMainApplicationOrThrow(manifest)['meta-data'] is not
added or remains empty. Keep the new test alongside the existing injects
com.google.android.geo.API_KEY meta-data into AndroidManifest case so the falsy
branch is explicitly covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@package/plugin/src/android.test.ts`:
- Around line 27-40: Add a regression test around applyGoogleMapsApiKey in
android.test.ts for the omitted googleMapsApiKey case, since the current test
only covers the happy path. Create a manifest with createEmptyManifest(), call
applyGoogleMapsApiKey with an undefined or otherwise falsy key, and assert that
AndroidConfig.Manifest.getMainApplicationOrThrow(manifest)['meta-data'] is not
added or remains empty. Keep the new test alongside the existing injects
com.google.android.geo.API_KEY meta-data into AndroidManifest case so the falsy
branch is explicitly covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6b265329-291e-4954-8cec-20aa1cd38ac0

📥 Commits

Reviewing files that changed from the base of the PR and between 196c72a and a90ac65.

📒 Files selected for processing (8)
  • README.md
  • docs/expo-setup.md
  • package/package.json
  • package/plugin/src/android.test.ts
  • package/plugin/src/android.ts
  • package/plugin/src/ios.test.ts
  • package/plugin/src/ios.ts
  • package/plugin/src/types.ts
✅ Files skipped from review due to trivial changes (2)
  • README.md
  • docs/expo-setup.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • package/plugin/src/ios.test.ts
  • package/plugin/src/types.ts
  • package/plugin/src/ios.ts
  • package/plugin/src/android.ts
  • package/package.json

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
package/plugin/src/types.ts (1)

1-25: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat blank strings as unset, not as real config.

Line 49 and Line 55 use ??, and the guards in this file accept any string. That means '' or whitespace from app.config.js disables the shared key fallback and can still generate empty location usage strings. That's a stupid foot-gun for env-backed config.

Possible fix
+function hasNonEmptyString(
+  value: string | false | undefined,
+): value is string {
+  return typeof value === 'string' && value.trim().length > 0;
+}
+
 export function wantsWhenInUseLocation(
   options: NitroMapsPluginOptions,
 ): options is NitroMapsPluginOptions & { locationPermission: string } {
-  return typeof options.locationPermission === 'string';
+  return hasNonEmptyString(options.locationPermission);
 }
 
 export function wantsAlwaysLocation(
   options: NitroMapsPluginOptions,
 ): options is NitroMapsPluginOptions & { locationAlwaysPermission: string } {
-  return typeof options.locationAlwaysPermission === 'string';
+  return hasNonEmptyString(options.locationAlwaysPermission);
 }
 
 export function resolveIosGoogleMapsApiKey(
   options: NitroMapsPluginOptions,
 ): string | undefined {
-  return options.iosGoogleMapsApiKey ?? options.googleMapsApiKey;
+  if (hasNonEmptyString(options.iosGoogleMapsApiKey)) {
+    return options.iosGoogleMapsApiKey;
+  }
+  return hasNonEmptyString(options.googleMapsApiKey)
+    ? options.googleMapsApiKey
+    : undefined;
 }
 
 export function resolveAndroidGoogleMapsApiKey(
   options: NitroMapsPluginOptions,
 ): string | undefined {
-  return options.androidGoogleMapsApiKey ?? options.googleMapsApiKey;
+  if (hasNonEmptyString(options.androidGoogleMapsApiKey)) {
+    return options.androidGoogleMapsApiKey;
+  }
+  return hasNonEmptyString(options.googleMapsApiKey)
+    ? options.googleMapsApiKey
+    : undefined;
 }

Also applies to: 45-56

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package/plugin/src/types.ts` around lines 1 - 25, Treat blank or
whitespace-only strings as unset in NitroMapsPluginOptions handling so empty
config values don’t override fallbacks or generate empty permissions. Update the
option guards and the fallback logic where googleMapsApiKey,
iosGoogleMapsApiKey, androidGoogleMapsApiKey, locationPermission, and
locationAlwaysPermission are read so they only count as set when the string
contains non-whitespace content. Keep the shared-key fallback behavior intact in
the plugin config path and avoid emitting usage descriptions for blank strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@package/plugin/src/types.ts`:
- Around line 1-25: Treat blank or whitespace-only strings as unset in
NitroMapsPluginOptions handling so empty config values don’t override fallbacks
or generate empty permissions. Update the option guards and the fallback logic
where googleMapsApiKey, iosGoogleMapsApiKey, androidGoogleMapsApiKey,
locationPermission, and locationAlwaysPermission are read so they only count as
set when the string contains non-whitespace content. Keep the shared-key
fallback behavior intact in the plugin config path and avoid emitting usage
descriptions for blank strings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 606af0b8-bbb1-4291-b089-ab92ea6ce43b

📥 Commits

Reviewing files that changed from the base of the PR and between a90ac65 and 57dd786.

📒 Files selected for processing (9)
  • README.md
  • example/app.config.js
  • package/android/README.md
  • package/package.json
  • package/plugin/src/android.test.ts
  • package/plugin/src/android.ts
  • package/plugin/src/ios.test.ts
  • package/plugin/src/ios.ts
  • package/plugin/src/types.ts
✅ Files skipped from review due to trivial changes (1)
  • package/android/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • package/plugin/src/android.test.ts
  • package/package.json
  • package/plugin/src/android.ts

@piotr-graczyk-dev piotr-graczyk-dev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkasprzyk17
jkasprzyk17 merged commit d3526ad into main Jun 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Expo config plugin for SDK 56+

2 participants