Skip to content

fix(react-native): make expo-file-system a peer dependency - #1795

Open
workflowhub26 wants to merge 1 commit into
appwrite:mainfrom
workflowhub26:fix/react-native-expo-file-system-peer
Open

fix(react-native): make expo-file-system a peer dependency#1795
workflowhub26 wants to merge 1 commit into
appwrite:mainfrom
workflowhub26:fix/react-native-expo-file-system-peer

Conversation

@workflowhub26

Copy link
Copy Markdown

What does this PR do?

Moves the React Native SDK's expo-file-system dependency from a hard pin ("18.*.*") to a peer dependency (">=18.0.0"), and adds an ambient type shim so the generated SDK still builds when peers are omitted.

Fixes appwrite/sdk-for-react-native#114.

Why

react-native-appwrite currently declares:

"dependencies": { "expo-file-system": "18.*.*" }

Apps on Expo SDK 54+ ship expo-file-system 19.x or newer, so npm installs a second, nested copy of 18.x under node_modules/react-native-appwrite/. Expo autolinking then mixes the two versions' native code and the app crashes at launch:

java.lang.NoClassDefFoundError: Failed resolution of: Lexpo/modules/filesystem/FilePermissionModule;

With a peer dependency, the host app owns the version and only one copy is ever installed. ">=18.0.0" deliberately has no upper bound — an upper bound would recreate this exact problem on the next Expo SDK release. Apps on older Expo SDKs are unaffected (their 18.x satisfies the range), and npm 7+ auto-installs missing peers for apps that do not list expo-file-system themselves.

Changes

  • templates/react-native/package.json.twigexpo-file-system moved from dependencies to peerDependencies as ">=18.0.0".
  • templates/react-native/src/expo-file-system-shim.d.ts.twig (new) — ambient module declaration covering exactly the API surface the SDK uses (readAsStringAsync, writeAsStringAsync, EncodingType, cacheDirectory). Same pattern as the existing react-native-shim.d.ts.twig: without it, npm ci --omit=peer && npm run build (as run in validation.yml) could no longer resolve the import. Like the react-native shim it is not part of the published files, so consumers keep getting real types from their own expo-file-system install.
  • src/SDK/Language/ReactNative.php — registers the new template file.
  • templates/react-native/package-lock.json.twig — regenerated with npm install --package-lock-only; the diff is exactly the moved dependency plus the "peer": true marker on the existing entry.

Verification

  • The template's FileSystem usage (mirroring services/template.ts.twig lines 200–208) typechecks against the shim alone with tsc --strict 5.9.3 and no expo-file-system installed — the situation npm ci --omit=peer produces.
  • Real-world confirmation of the underlying bug: our production Expo SDK 57 app needs an npm overrides entry to force a single expo-file-system copy; without it, expo-doctor flags the duplicate and a native Android build crashes at launch exactly as described in Added automated deployment to NPM #114.

Related

appwrite/sdk-for-react-native#112 is adjacent but separate: the SDK calls the legacy FileSystem API, which newer expo-file-system versions expose via expo-file-system/legacy. This PR does not change runtime behavior — it only stops the forced second native copy.

🤖 Generated with Claude Code

The hard pin "expo-file-system": "18.*.*" forces npm to install a
second, nested copy of expo-file-system in apps on Expo SDK 54+,
where the app itself uses 19.x or newer. Expo autolinking then mixes
the two versions' native code and the app crashes at launch
(NoClassDefFoundError: expo.modules.filesystem.FilePermissionModule).

Moving the dependency to peerDependencies (">=18.0.0") lets the host
app own the version, so only one copy is ever installed.

The new ambient shim (same pattern as react-native-shim.d.ts) keeps
the generated SDK compiling under "npm ci --omit=peer", where peer
packages are not installed.

Fixes appwrite/sdk-for-react-native#114

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR moves expo-file-system from a pinned React Native dependency to a host-owned peer and adds a build-only ambient declaration for peer-omitted validation. The widened range, however, accepts versions whose root module no longer provides the generated SDK’s legacy filesystem API.

  • Registers and emits a minimal expo-file-system TypeScript shim.
  • Moves expo-file-system to peerDependencies with a >=18.0.0 range.
  • Regenerates lockfile peer metadata.

Confidence Score: 4/5

This PR should not merge until the peer compatibility range or generated filesystem import is aligned with expo-file-system 19+, otherwise uploads fail for the Expo versions this change targets.

The host-provided 19+ package satisfies the new peer range, but generated upload code still invokes APIs that newer releases expose only through the legacy entrypoint, with the ambient shim hiding the mismatch during compilation.

Files Needing Attention: templates/react-native/package.json.twig, templates/react-native/src/expo-file-system-shim.d.ts.twig

Important Files Changed

Filename Overview
templates/react-native/package.json.twig Moves expo-file-system to a peer, but the unbounded range accepts 19+ despite generated code still using the legacy root-module API.
templates/react-native/src/expo-file-system-shim.d.ts.twig Enables peer-omitted type builds but masks the runtime API mismatch for accepted newer peer versions.
src/SDK/Language/ReactNative.php Correctly registers the new default-scope shim template for generated React Native SDKs.
templates/react-native/package-lock.json.twig Updates root dependency classification and marks the existing expo-file-system lock entry as a peer.

Fix all with Greploop

Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
### Issue 1
templates/react-native/package.json.twig:48
**Peer range exposes legacy API break**

When an Expo SDK 54+ application supplies `expo-file-system` 19+, the new peer range accepts that implementation while generated services still import legacy filesystem APIs from the package root, causing multipart file uploads to fail at runtime.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(react-native): make expo-file-system..." | Re-trigger Greptile

},
"peerDependencies": {
"expo": "*",
"expo-file-system": ">=18.0.0",

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.

P1 Peer range exposes legacy API break

When an Expo SDK 54+ application supplies expo-file-system 19+, the new peer range accepts that implementation while generated services still import legacy filesystem APIs from the package root, causing multipart file uploads to fail at runtime.

Knowledge Base Used: Templates

Prompt To Fix With AI
This is a comment left during a code review.
Path: templates/react-native/package.json.twig
Line: 48

Comment:
**Peer range exposes legacy API break**

When an Expo SDK 54+ application supplies `expo-file-system` 19+, the new peer range accepts that implementation while generated services still import legacy filesystem APIs from the package root, causing multipart file uploads to fail at runtime.

**Knowledge Base Used:** [Templates](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/sdk-generator/-/docs/templates.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

@workflowhub26

Copy link
Copy Markdown
Author

Author here. The automated review is right that the peer range and the import path have to move together, and I'd rather fix that than argue it. Adding the specific evidence, because most of it is already committed in this repo.

1. main still imports the legacy API from the package root

templates/react-native/src/services/template.ts.twig:5

import * as FileSystem from 'expo-file-system';

Since expo-file-system made the modern API the default and moved the previous one to expo-file-system/legacy (expo/expo#38404), those root exports are stubs. The package says so itself — from expo-file-system/build/legacyWarnings.d.ts in an installed 57.0.5:

@deprecated Use `new File().text()` or import this method from
`expo-file-system/legacy`. This method will throw in runtime.
export declare function readAsStringAsync(...)

So readAsStringAsync, cacheDirectory and EncodingType still resolve and still compile, and throw when called. Chunked uploads are where users hit it, because that path calls all three.

The expo-file-system-shim.d.ts this PR adds declares exactly those symbols on the root module, which means tsc stops seeing the mismatch. That is a fair criticism of my patch.

2. The conflict is already in templates/react-native/package-lock.json.twig

  • line 12 — what the generated SDK asks for: "expo-file-system": "18.*.*"
  • line 3582 — what node_modules/expo (SDK 57, entry starts line 3560) asks for right beside it: "expo-file-system": "~57.0.2"

Two incompatible ranges for the same native module, in one lockfile. A native build may only contain one copy, so consumers on a current Expo need an overrides entry to install the SDK at all. That is what this PR was trying to remove, and the review is correct that it doesn't finish the job.

3. No single import path covers >=18.0.0

root import /legacy import
18.x works subpath does not exist yet
19+ / 5x throws at runtime works

expo-file-system/legacy was created by the same change that made the modern API the default, so it is not available in the 18.x half of the proposed range. That means #1691 (switch the import to /legacy) and this PR (widen the peer) are each half a fix, and even applied together they don't produce a consistent state across >=18.0.0.

Options

Happy to implement whichever you prefer:

  • (a) peer floor at the SDK-54-aligned version + /legacy import, dropping 18.x. Simplest, and matches what Expo 54+ users actually have installed.
  • (b) keep >=18.0.0 and resolve at runtime — try expo-file-system/legacy, fall back to the root. Covers both, at the cost of a try/catch in generated code.
  • (c) peer >=18.0.0 <19.0.0 + root import. Consistent, but leaves Expo 54+ users where they are today.

I'd drop the .d.ts shim in all three — it only exists to make the current inconsistency compile.

For what it's worth, I've been running (a) against react-native-appwrite@0.34.0 via patch-package in an Expo SDK 57 app, together with an overrides pin, and both pieces are needed today. Glad to reshape this PR toward whichever option you'd take, or to close it if you'd rather solve it differently.

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.

App crashes at launch on Expo SDK 54: expo-file-system@18 pin conflicts with the app's expo-file-system 19

2 participants