Skip to content

Navigation Menu

Sign in
Sign up

fix(link-assets): match asset extensions case-insensitively - #2847

Open
ahmdshrif wants to merge 1 commit into
react-native-community:main from
ahmdshrif:fix-link-assets-uppercase-extension
Open

fix(link-assets): match asset extensions case-insensitively #2847
ahmdshrif wants to merge 1 commit into
react-native-community:main from
ahmdshrif:fix-link-assets-uppercase-extension

Conversation

@ahmdshrif

@ahmdshrif ahmdshrif commented Aug 30, 2026

Copy link
Copy Markdown

Summary

link-assets decides how to link each asset by bucketing it on its file extension, in packages/cli-link-assets/src/tools/linkPlatform/index.ts:

filter: (asset) => path.extname(asset.path) === `.${fileExt}`,

fileExt comes from the lower-case lists in fileTypes.ts (otf, ttf, png, jpg, gif, mp3), so the comparison is case-sensitive. An asset whose extension is cased differently on disk — Lato-Regular.TTF, Photo.PNG, Clip.MP3 — matches no bucket and falls through to the custom one, which is meant for unknown file types.

That is a silent mislink, not an error:

  • Android — the font is copied to app/src/main/assets/custom/ instead of app/src/main/res/font/; no res/font/<family>.xml is generated and no ReactFontManager.getInstance().addCustomFont(...) call is inserted into MainApplication. An image lands in the same folder instead of res/drawable/.
  • iOS — the file is still added to the Xcode Resources group, but copyAssets receives isFontAsset: false, so it is never appended to UIAppFonts in Info.plist. The font is inside the app bundle and cannot be used at runtime.

Uppercase font extensions are common (a lot of foundries and font sites ship .TTF/.OTF), and this package's own README documents the supported types as "Fonts (OTF, TTF)" and "Images (JPG, PNG, GIF)", so .TTF is a reasonable thing for a user to have in assets/.

The same case-sensitive comparison is in tools/manifest/migrations/migration2.ts, which decides whether a previously linked asset is a font that needs relinking to XML resources, so an uppercase-extension font in an old manifest is skipped by that migration too.

This normalizes the extension once, in a getAssetExtension() helper next to the type lists, and uses it at both comparison sites. Assets whose extension is already lower-case are completely unaffected.

Test Plan

Two tests were added to packages/cli-link-assets/src/__tests__/linkAssets.test.ts, following the existing end-to-end pattern (a real temp project, real font fixtures, linkAssets() run for real):

  • should link a font asset whose extension is upper-cased — links assets/shared/fonts/Montserrat-Regular.TTF and asserts that res/font/montserrat.xml references it, that res/font/montserrat_regular.ttf exists, that it is not in assets/custom/, that MainApplication.kt gets the addCustomFont call, and that Info.plist lists it under UIAppFonts.
  • should link an image asset whose extension is upper-cased — links assets/shared/Upper Image.PNG and asserts it ends up in res/drawable/upper_image.png and not in assets/custom/.

Before the fix (tests applied, sources unchanged) both fail, showing the asset in the wrong place:

✕ should link a font asset whose extension is upper-cased
 ENOENT: no such file or directory, open '.../android/app/src/main/res/font/montserrat.xml'
✕ should link an image asset whose extension is upper-cased
 expect(received).toBe(expected) // res/drawable/upper_image.png
 Expected: true
 Received: false
 (instrumented run)
 CUSTOM: [ 'Montserrat-Regular.TTF', 'TestSample Document.pdf' ]
 RESFONT: []
 UIAppFonts contains TTF? false
Tests: 2 failed, 6 passed, 8 total

After the fix:

Tests: 8 passed, 8 total
Snapshots: 32 passed, 32 total

All 32 pre-existing snapshots pass unchanged, which is the regression guard for lower-case extensions.

Whole repo:

  • node_modules/.bin/jest packages55 suites, 315 passed, 1 todo
  • node ./scripts/buildTs.js → clean
  • node_modules/.bin/eslint packages/cli-link-assets/src --ext .ts → clean

Checklist

  • Documentation is up to date.
  • Follows commit message convention described in CONTRIBUTING.md.
  • For functional changes, my test plan has linked these CLI changes into a local react-native checkout (instructions).

Note on the last box: verification here was the package's own end-to-end test suite, which drives linkAssets() against a real temporary Android/iOS project fixture rather than a linked react-native checkout. Happy to run the linked-checkout test plan if you'd like that on the record.

`link-assets` dispatches every asset to a per-extension bucket by
comparing `path.extname()` against the lower-case type lists in
`fileTypes.ts`. The comparison is case-sensitive, so an asset whose
extension is cased differently on disk — `Lato-Regular.TTF`,
`Photo.PNG` — never matches its bucket and falls through to the
"custom" one instead.
The result is a silent mislink:
- on Android the font is copied to `app/src/main/assets/custom/`
 rather than `app/src/main/res/font/`, no `res/font/<family>.xml` is
 generated and no `ReactFontManager.addCustomFont()` call is added to
 `MainApplication`, and an image lands there instead of
 `res/drawable/`;
- on iOS the file is added to the Xcode `Resources` group but
 `isFontAsset` is false, so it is never appended to `UIAppFonts` and
 the font ships inside the bundle without being loadable at runtime.
Normalize the extension once, in `getAssetExtension()`, and use it both
where `linkPlatform` builds its filters and in `migration2`, which
flags previously linked fonts for relinking with the same comparison.
Extensions that were already lower-case are unaffected: all 32 existing
snapshots pass unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@thymikee thymikee Awaiting requested review from thymikee thymikee is a code owner

Assignees

No one assigned

Labels

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

1 participant

AltStyle によって変換されたページ (->オリジナル) /