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 b514a8adda4..84d7a05de42 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 109fa3bde38..0b2d879bff0 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;