From bbda2f6d032677ba1e395eabd97f054eb5667033 Mon Sep 17 00:00:00 2001 From: Christian Falch Date: Thu, 29 Oct 2026 18:19:34 +0100 Subject: [PATCH] SwiftPM autolinking: don't warn about spm fields mirrored in swiftpmConfig A library that supports React Native 0.87 must keep its SwiftPM settings in the deprecated react-native.config.js 'spm' block, because 0.87 reads only that block. To support 0.88 too, it also declares them in package.json 'swiftpmConfig'. 0.88 warned about every field in the 'spm' block, so these libraries got a warning they could not fix. Now a field whose value is the same in both places is not warned about. Other fields in the 'spm' block are warned about as before. --- .../spm/__tests__/swiftpm-config-test.js | 53 +++++++++++++++++++ .../scripts/spm/swiftpm-config.js | 25 ++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/packages/react-native/scripts/spm/__tests__/swiftpm-config-test.js b/packages/react-native/scripts/spm/__tests__/swiftpm-config-test.js index b514a8adda43..84d7a05de42c 100644 --- a/packages/react-native/scripts/spm/__tests__/swiftpm-config-test.js +++ b/packages/react-native/scripts/spm/__tests__/swiftpm-config-test.js @@ -173,6 +173,59 @@ describe('readSwiftpmConfig', () => { warnSpy.mockRestore(); } }); + + it('says nothing when swiftpmConfig mirrors every field of the spm block', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + try { + const settings = { + name: 'RNSentry', + dependencies: ['react-native-worklets'], + }; + const root = makeRoot({name: 'lib', swiftpmConfig: settings}); + const config = readSwiftpmConfig(root, { + spm: {name: 'RNSentry', dependencies: ['react-native-worklets']}, + }); + expect(config).toEqual(settings); + expect(warnSpy).not.toHaveBeenCalled(); + } finally { + warnSpy.mockRestore(); + } + }); + + it('still warns about a field the two places disagree on', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + try { + const root = makeRoot({ + name: 'lib', + swiftpmConfig: {name: 'RNSVG', dependencies: ['a']}, + }); + const config = readSwiftpmConfig(root, { + spm: {name: 'OldName', dependencies: ['a']}, + }); + expect(config).toEqual({name: 'RNSVG', dependencies: ['a']}); + expect(warnSpy).toHaveBeenCalledTimes(1); + const message = warnSpy.mock.calls[0].join(' '); + expect(message).toContain("deprecated 'spm' block (name)"); + } finally { + warnSpy.mockRestore(); + } + }); + + it('names only the fields the spm block alone declares', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + try { + const root = makeRoot({name: 'lib', swiftpmConfig: {name: 'RNSVG'}}); + const config = readSwiftpmConfig(root, { + spm: {name: 'RNSVG', dependencies: ['a']}, + }); + expect(config).toEqual({name: 'RNSVG', dependencies: ['a']}); + expect(warnSpy).toHaveBeenCalledTimes(1); + const message = warnSpy.mock.calls[0].join(' '); + expect(message).toContain("deprecated 'spm' block (dependencies)"); + } finally { + warnSpy.mockRestore(); + } + }); }); describe('readSwiftpmConfig (unknown keys)', () => { diff --git a/packages/react-native/scripts/spm/swiftpm-config.js b/packages/react-native/scripts/spm/swiftpm-config.js index 109fa3bde38c..0b2d879bff07 100644 --- a/packages/react-native/scripts/spm/swiftpm-config.js +++ b/packages/react-native/scripts/spm/swiftpm-config.js @@ -11,6 +11,7 @@ 'use strict'; const {isValidSwiftName, makeLogger} = require('./spm-utils'); +const assert = require('node:assert'); const fs = require('node:fs'); const path = require('node:path'); @@ -100,6 +101,20 @@ function declaredFields( : SWIFTPM_FIELDS.filter(field => config[field] !== undefined); } +// util.isDeepStrictEqual is missing from the Flow library definitions. +function sameField( + a /*: ?{readonly [string]: unknown} */, + b /*: ?{readonly [string]: unknown} */, + field /*: string */, +) /*: boolean */ { + try { + assert.deepStrictEqual(a?.[field], b?.[field]); + return true; + } catch { + return false; + } +} + function stringList(value /*: unknown */) /*: Array */ { return Array.isArray(value) ? value.filter(v => typeof v === 'string') : []; } @@ -155,8 +170,14 @@ function readSwiftpmConfig( } const deprecated = objectField(rnConfig, 'spm'); const deprecatedFields = declaredFields(deprecated); - if (deprecatedFields.length > 0) { - reportDeprecation(root, deprecatedFields); + // A library that supports React Native versions reading only the `spm` + // block must declare a field in both places, so an identical copy is not + // something it can act on. + const unmirroredFields = deprecatedFields.filter( + field => !sameField(fromPackageJson, deprecated, field), + ); + if (unmirroredFields.length > 0) { + reportDeprecation(root, unmirroredFields); } if (fromPackageJson == null) { return deprecatedFields.length > 0 ? deprecated : null;