Hold an app's spm.modules names to the same rules as a library's - #58060
Open
chrfalch wants to merge 2 commits into
Open
Hold an app's spm.modules names to the same rules as a library's#58060chrfalch wants to merge 2 commits into
chrfalch wants to merge 2 commits into
Conversation
|
Caution Invalid Base Branch The base branch for this PR is |
A dependency's Swift name is derived from its npm package name with the scope dropped, which makes two collisions unavoidable: `@powersync/react-native` derives `ReactNative`, one of React Native's own names, and `@a/foo` and `@b/foo` both derive `Foo`. Either was emitted into the package graph as-is, and SwiftPM then failed deep inside dependency resolution with a duplicate-name error that named neither the library nor the react-native.config.js that caused it. The scope that was dropped is the fix. A scoped dep whose derived name is reserved gets the TitleCased scope prepended (`PowersyncReactNative`), and deps that resolved to the same name get it prepended too (`AFoo`, `BFoo`), so the library author has nothing to do. Every disambiguation logs one line naming the package, the name it would have taken and the name it got. Nothing can regress on this — both collisions fail SwiftPM resolution today, so no working library carries such a name and no consumer imports headers under it. The two cases need different shapes. A reserved name is decidable per dep, so it resolves in `resolveSwiftName`. A collision with another dep is not visible from there, so it is a pass over the resolved set: group by name, and prepend the scope to every scoped member of a group larger than one. Every member moves rather than one arbitrary winner staying put, since there is no non-arbitrary winner. Two members never move: a name the author set with `spm.name` (their choice wins, and the others move around it) and an unscoped one (no scope to borrow). The pass runs once, and then the whole set is validated — this is the part that has to be right. A borrowed scope can land on a name another dep already holds (`@a/foo` → `AFoo`, next to a package `a-foo`) or on a reserved one, and two libraries silently sharing a name is worse than the error this replaces. So both existing checks now run over the final set, and anything a scope could not resolve still fails with the message it did before: two unscoped deps deriving the same name, an explicit `spm.name` that is reserved, a group whose only scoped member's new name is taken. Retrying instead of failing would trade a diagnosable error for a name nobody can predict. Reserving the names React Native puts in a manifest is what makes the first case diagnosable at all. Matching is case-insensitive throughout: a name that differs from another only in case is not distinct enough for the build to keep the two apart. The reserved check runs before the dep-vs-dep one, so the more specific diagnosis wins, and it runs for a library that ships an autolinking plugin too: `spm scaffold` knows nothing about plugins, so an exemption there would leave the two commands disagreeing about the same library. `SpmNameCollisionError` distinguishes a misconfiguration from a resolution failure, so `scaffoldAll` — which degrades to the direct deps when a transitive dep can't be found — still surfaces it instead of scaffolding manifests SPM will reject. Its remote package config moves out of the same try for the same reason. Every Swift name reaching a manifest now comes from that one resolved set: the autolinker's two `toSwiftName` fallbacks would have re-derived the pre-disambiguation name and emitted a reference nothing matches, so they are replaced by a required lookup that fails loudly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
chrfalch
force-pushed
the
spm/reserved-name-guard
branch
from
August 24, 2026 09:08
c9aa6ae to
b8cc821
Compare
`spm.modules` names go into the generated manifest exactly as written, with no validation at all: a name React Native reserves produced the same opaque SwiftPM duplicate-name failure a library's name did, a name that is not a Swift identifier produced a manifest SwiftPM refuses to parse, and two modules — or a module and an autolinked library — could quietly claim the same target name. Run the reserved-name and charset checks the library surface already has, and check each name against the targets already emitted, so the app author is told which entry to rename in their own react-native.config.js. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
chrfalch
force-pushed
the
spm/spm-modules-guard
branch
2 times, most recently
from
August 24, 2026 09:11
8b362bc to
cdaf50b
Compare
chrfalch
force-pushed
the
spm/reserved-name-guard
branch
from
August 24, 2026 13:45
b8cc821 to
881411a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
An app declares extra native modules through spm.modules in its react-native.config.js. Those names went into the generated package graph unvalidated, and two of the ways they can go wrong fail silently.
This PR fixes this by using the same Swift name collision detection/resolving as we introduced in #58044
Changelog:
[IOS] [FIXED] - Reject colliding or invalid spm.modules names instead of silently dropping a module from the build
Test Plan:
✅ Unit tests/CI