@depup/react-native 0.87.0-depup.0 → 0.87.1-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/Libraries/Animated/AnimatedEvent.js +1 -1
  2. package/Libraries/Animated/AnimatedImplementation.js +6 -2
  3. package/Libraries/Animated/nodes/AnimatedNode.js +1 -1
  4. package/Libraries/Animated/nodes/AnimatedValue.js +2 -2
  5. package/Libraries/Core/ReactNativeVersion.js +1 -1
  6. package/README.md +3 -3
  7. package/React/Base/RCTVersion.m +1 -1
  8. package/ReactAndroid/gradle.properties +1 -1
  9. package/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.kt +7 -3
  10. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
  11. package/ReactCommon/cxxreact/ReactNativeVersion.h +2 -2
  12. package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm +1 -1
  13. package/changes.json +2 -2
  14. package/package.json +12 -12
  15. package/scripts/cocoapods/rndependencies.rb +31 -25
  16. package/scripts/replace-rncore-version.js +18 -2
  17. package/scripts/setup-apple-spm.js +28 -12
  18. package/scripts/spm/autolinking-plugins.js +80 -0
  19. package/scripts/spm/download-spm-artifacts.js +64 -6
  20. package/scripts/spm/expand-spm-dependencies.js +249 -30
  21. package/scripts/spm/generate-spm-autolinking.js +157 -40
  22. package/scripts/spm/generate-spm-package.js +30 -18
  23. package/scripts/spm/generate-spm-xcodeproj.js +595 -71
  24. package/scripts/spm/scaffold-package-swift.js +65 -24
  25. package/scripts/spm/spm-pbxproj.js +158 -28
  26. package/scripts/spm/spm-types.js +29 -3
  27. package/scripts/spm/spm-utils.js +117 -2
  28. package/sdks/.hermesv1version +1 -1
  29. package/sdks/hermes-engine/version.properties +1 -1
  30. package/types_generated/Libraries/Animated/AnimatedImplementation.d.ts +2 -2
  31. package/types_generated/Libraries/Animated/nodes/AnimatedNode.d.ts +4 -1
  32. package/types_generated/Libraries/Animated/nodes/AnimatedValue.d.ts +3 -3
  33. package/scripts/spm/__doc__/rfc-spm-xcframework.md +0 -707
  34. package/scripts/spm/__doc__/spm-autolinking-plugins.md +0 -244
  35. package/scripts/spm/__doc__/spm-header-paths-contract.md +0 -97
  36. package/scripts/spm/__doc__/spm-plugins-assessment.md +0 -128
  37. package/scripts/spm/__doc__/spm-scripts.md +0 -486
@@ -393,15 +393,63 @@ async function resolveRNDepsArtifact(
393
393
  return {url: snapshotUrl, version};
394
394
  }
395
395
 
396
+ /**
397
+ * Resolves the `hermes-compiler` npm package's version from THIS project's own
398
+ * node_modules — the exact same lookup generate-spm-xcodeproj.js's
399
+ * resolveHermesCliPathSetting() uses to find the hermesc binary that will
400
+ * compile the JS bundle, and the same one react-native-xcode.sh falls back to
401
+ * for SwiftPM builds. Returns null when the package isn't resolvable (e.g.
402
+ * USE_HERMES=false apps that never installed it) so the caller can fall back
403
+ * to the npm dist-tag lookup.
404
+ */
405
+ function resolveLocalHermesCompilerVersion(
406
+ rnRoot /*: string */,
407
+ ) /*: string | null */ {
408
+ try {
409
+ const pkgPath = require.resolve('hermes-compiler/package.json', {
410
+ paths: [rnRoot],
411
+ });
412
+ // $FlowFixMe[incompatible-type] JSON.parse returns any
413
+ const pkg /*: {version: string} */ = JSON.parse(
414
+ fs.readFileSync(pkgPath, 'utf8'),
415
+ );
416
+ assertSafeVersion(pkg.version, 'local hermes-compiler/package.json');
417
+ return pkg.version;
418
+ } catch (error) {
419
+ // A MODULE_NOT_FOUND resolution failure is the expected case (e.g.
420
+ // USE_HERMES=false apps that never installed hermes-compiler) — fall back
421
+ // silently. Any other failure means hermes-compiler IS installed but its
422
+ // package.json is unreadable/malformed or carries an unsafe version; warn
423
+ // loudly rather than silently regressing to the live latest-v1 dist-tag,
424
+ // which would re-introduce the version-skew crash this resolves (#57917).
425
+ if (error.code !== 'MODULE_NOT_FOUND') {
426
+ log(
427
+ ` WARNING: hermes-compiler is installed but its version could not be resolved (${error.message}); falling back to the latest-v1 dist-tag, which may not match the pinned hermesc and can crash at launch with "Wrong bytecode version".`,
428
+ );
429
+ }
430
+ return null;
431
+ }
432
+ }
433
+
396
434
  /**
397
435
  * Returns {url, version} for Hermes. Hermes uses its own version space
398
436
  * decoupled from React Native's nightly cadence — RN's `hermes-compiler`
399
437
  * npm package publishes a `latest-v1` dist-tag that always resolves to a
400
- * binary that's been built and uploaded to Maven. Our default mirrors RN's
401
- * CocoaPods prebuild path (see scripts/ios-prebuild/hermes.js):
438
+ * binary that's been built and uploaded to Maven.
402
439
  *
403
- * HERMES_VERSION unset → 'latest-v1' dist-tag
404
- * HERMES_VERSION=latest-v1 → same (explicit)
440
+ * HERMES_VERSION unset → version pinned by the locally installed
441
+ * hermes-compiler package (node_modules).
442
+ * This is the SAME source
443
+ * resolveHermesCliPathSetting() reads for
444
+ * HERMES_CLI_PATH, so the downloaded VM and
445
+ * the hermesc that compiles the JS bundle
446
+ * always agree — a mismatched pair crashes at
447
+ * launch with "Wrong bytecode version" (#57917).
448
+ * Falls back to the 'latest-v1' npm dist-tag
449
+ * (RN's CocoaPods prebuild default; see
450
+ * scripts/ios-prebuild/hermes.js) only when
451
+ * hermes-compiler isn't locally resolvable.
452
+ * HERMES_VERSION=latest-v1 → 'latest-v1' dist-tag (explicit)
405
453
  * HERMES_VERSION=nightly → hermes-compiler@nightly dist-tag
406
454
  * HERMES_VERSION=<literal> → use that version verbatim
407
455
  *
@@ -413,8 +461,17 @@ async function resolveHermesArtifact(
413
461
  rnVersion /*: string */,
414
462
  flavor /*: string */,
415
463
  rawVersion /*: string | null */,
464
+ rnRoot /*: string */,
416
465
  ) /*: Promise<ResolvedArtifact> */ {
417
- let version = process.env.HERMES_VERSION ?? 'latest-v1';
466
+ let version = process.env.HERMES_VERSION;
467
+
468
+ if (version == null) {
469
+ const localVersion = resolveLocalHermesCompilerVersion(rnRoot);
470
+ if (localVersion != null) {
471
+ log(` Using locally pinned hermes-compiler: ${localVersion}`);
472
+ }
473
+ version = localVersion ?? 'latest-v1';
474
+ }
418
475
 
419
476
  if (version === 'nightly') {
420
477
  version = await resolveNightlyVersion('hermes-compiler');
@@ -1119,7 +1176,7 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
1119
1176
  label: 'hermes',
1120
1177
  name: 'hermes-engine',
1121
1178
  resolve: () =>
1122
- resolveHermesArtifact(resolvedRnVersion, flavor, rawVersion),
1179
+ resolveHermesArtifact(resolvedRnVersion, flavor, rawVersion, rnRoot),
1123
1180
  sharedName: (v /*: string */) => `hermes-ios-${v}-${flavor}.tar.gz`,
1124
1181
  },
1125
1182
  ];
@@ -1390,6 +1447,7 @@ module.exports = {
1390
1447
  main,
1391
1448
  resolveCacheSlotVersion,
1392
1449
  resolveHermesArtifact,
1450
+ resolveLocalHermesCompilerVersion,
1393
1451
  REQUIRED_ARTIFACTS,
1394
1452
  validateArtifactsCache,
1395
1453
  // Exposed for unit tests (pure / fetch-stubbable helpers).
@@ -10,10 +10,12 @@
10
10
 
11
11
  'use strict';
12
12
 
13
- const {toSwiftName} = require('./spm-utils');
13
+ const {RESERVED_SWIFT_NAMES, makeLogger, toSwiftName} = require('./spm-utils');
14
14
  const fs = require('fs');
15
15
  const path = require('path');
16
16
 
17
+ const {warn} = makeLogger('expand-spm-dependencies');
18
+
17
19
  /**
18
20
  * expand-spm-dependencies.js — Resolves transitive native deps declared via
19
21
  * `spm.dependencies` in a library's react-native.config.js.
@@ -32,7 +34,7 @@ const path = require('path');
32
34
  * list with autolinking-shaped entries so the downstream pipeline can convert
33
35
  * each to an SPM target without further branching.
34
36
  *
35
- * I/O is injected (readConfig, resolveDep) so the logic stays pure and
37
+ * I/O is injected (readConfig, resolveDep, log) so the logic stays pure and
36
38
  * testable.
37
39
  */
38
40
 
@@ -44,52 +46,232 @@ import type {AutolinkedDep} from './spm-types';
44
46
  type RnConfig = {...};
45
47
  type ReadConfig = (root: string) => ?RnConfig;
46
48
  type ResolveDep = (name: string, fromRoot: string) => ?string;
49
+ type Log = (message: string) => void;
50
+ // Keyed by lower case, valued with the canonical spelling: two names differing
51
+ // only in case are not distinct enough for the build to keep the two apart.
52
+ type ReservedNames = ReadonlyMap<string, string>;
47
53
  type Options = {
48
54
  readConfig: ReadConfig,
49
55
  resolveDep: ResolveDep,
56
+ // Names to reserve alongside RESERVED_SWIFT_NAMES, supplied by the caller
57
+ // (remote mode relabels the RN package) since this module reads no config.
58
+ extraReservedNames?: ?ReadonlyArray<string>,
59
+ log?: ?Log,
50
60
  };
51
61
  */
52
62
 
53
- // Validates and returns the Swift target name for a dep. Falls back to
54
- // toSwiftName(npmName) when no override is set. The override is the dep's
55
- // `react-native.config.js` `spm.name`, intended for libraries whose import
56
- // prefix differs from the auto-derived name (e.g. `react-native-worklets`
57
- // publishes headers under `<worklets/...>` via the podspec `s.header_dir`,
58
- // so the SPM target name should be `worklets`, not `ReactNativeWorklets`).
63
+ /**
64
+ * A misconfiguration rather than a resolution failure: scaffoldAll degrades past
65
+ * a transitive dep it cannot find, but must still surface this.
66
+ */
67
+ class SpmNameCollisionError extends Error {
68
+ constructor(message /*: string */) {
69
+ super(message);
70
+ this.name = 'SpmNameCollisionError';
71
+ }
72
+ }
73
+
74
+ // The charset `spm.name` must satisfy — permissive on purpose, since it has to
75
+ // admit header-dir style (lowercase with hyphens) as well as Swift identifiers.
76
+ // Shared with the app's own `spm.modules` names.
77
+ function isValidSwiftName(name /*: unknown */) /*: boolean */ {
78
+ return typeof name === 'string' && /^[A-Za-z_][A-Za-z0-9_-]*$/.test(name);
79
+ }
80
+
81
+ function reservedSwiftNames(
82
+ extraReservedNames /*: ?ReadonlyArray<string> */,
83
+ ) /*: ReservedNames */ {
84
+ return new Map(
85
+ [...RESERVED_SWIFT_NAMES, ...(extraReservedNames ?? [])].map(name => [
86
+ name.toLowerCase(),
87
+ name,
88
+ ]),
89
+ );
90
+ }
91
+
92
+ // The scope-borrowed form of a name: `@powersync/react-native`'s `ReactNative`
93
+ // becomes `PowersyncReactNative`.
94
+ function scopeBorrowedName(
95
+ npmName /*: string */,
96
+ swiftName /*: string */,
97
+ ) /*: ?string */ {
98
+ const scope = /^@([^/]+)\//.exec(npmName)?.[1];
99
+ return scope == null ? null : `${toSwiftName(scope)}${swiftName}`;
100
+ }
101
+
102
+ // The Swift target name for one dep, judged in isolation. `spm.name` is for
103
+ // libraries whose import prefix differs from the derived name:
104
+ // `react-native-worklets` ships headers as `<worklets/...>` (podspec
105
+ // `s.header_dir`), so its target is `worklets`, not `ReactNativeWorklets`. A
106
+ // derived name that lands on a reserved one borrows the npm scope instead.
59
107
  function resolveSwiftName(
60
108
  npmName /*: string */,
61
109
  config /*: ?RnConfig */,
110
+ reserved /*: ReservedNames */,
111
+ log /*:: ?: ?Log */,
62
112
  ) /*: string */ {
63
113
  // $FlowFixMe[prop-missing] config has dynamic shape
64
114
  const override = config?.spm?.name;
65
- if (override == null) {
66
- return toSwiftName(npmName);
115
+ if (override != null) {
116
+ if (typeof override !== 'string' || override.length === 0) {
117
+ throw new Error(
118
+ `react-native autolinking: '${npmName}' has an invalid 'spm.name' override: expected a non-empty string, got ${JSON.stringify(override)}.`,
119
+ );
120
+ }
121
+ if (!isValidSwiftName(override)) {
122
+ throw new Error(
123
+ `react-native autolinking: '${npmName}' has an invalid 'spm.name' override '${override}': must start with a letter or underscore and contain only letters, digits, underscores, or hyphens.`,
124
+ );
125
+ }
126
+ return override;
67
127
  }
68
- if (typeof override !== 'string' || override.length === 0) {
69
- throw new Error(
70
- `react-native autolinking: '${npmName}' has an invalid 'spm.name' override: expected a non-empty string, got ${JSON.stringify(override)}.`,
71
- );
128
+
129
+ const derived = toSwiftName(npmName);
130
+ if (!reserved.has(derived.toLowerCase())) {
131
+ return derived;
72
132
  }
73
- // Accept Swift-identifier style (TitleCase / snake_case) and header-dir
74
- // style (lowercase, optional hyphens). Reject whitespace, slashes, and
75
- // other characters that would break SPM target / module identifiers.
76
- if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(override)) {
77
- throw new Error(
78
- `react-native autolinking: '${npmName}' has an invalid 'spm.name' override '${override}': must start with a letter or underscore and contain only letters, digits, underscores, or hyphens.`,
79
- );
133
+ const disambiguated = scopeBorrowedName(npmName, derived);
134
+ if (disambiguated == null || reserved.has(disambiguated.toLowerCase())) {
135
+ return derived;
136
+ }
137
+ log?.(
138
+ `'${npmName}' would take React Native's reserved name '${derived}', so its npm scope is prepended: '${disambiguated}'. ` +
139
+ `Set 'spm.name' in ${npmName}'s react-native.config.js to choose the name yourself.`,
140
+ );
141
+ return disambiguated;
142
+ }
143
+
144
+ function assertNameNotReserved(
145
+ swiftName /*: string */,
146
+ reserved /*: ReservedNames */,
147
+ labels /*: {label: string, remedy: string} */,
148
+ ) /*: void */ {
149
+ const reservedName = reserved.get(swiftName.toLowerCase());
150
+ if (reservedName == null) {
151
+ return;
152
+ }
153
+ // Vaguer about the case clash than the dep-vs-dep message on purpose: this
154
+ // set spans package identities and product names, which collide differently.
155
+ throw new SpmNameCollisionError(
156
+ `react-native autolinking: SPM Swift name collision: ${labels.label} resolves to '${swiftName}', ` +
157
+ (reservedName === swiftName
158
+ ? `which React Native reserves for its own SPM package and products.`
159
+ : `which differs from React Native's reserved '${reservedName}' only in case — not distinct enough for the build to keep the two apart.`) +
160
+ ` ${labels.remedy}`,
161
+ );
162
+ }
163
+
164
+ /**
165
+ * Throws when `swiftName` is one React Native's own manifests use. `remedy` is
166
+ * the fix: a library sets `spm.name`, an app renames its `spm.modules` entry.
167
+ */
168
+ function assertSwiftNameNotReserved(
169
+ swiftName /*: string */,
170
+ options /*: {
171
+ label: string,
172
+ remedy: string,
173
+ extraReservedNames?: ?ReadonlyArray<string>,
174
+ } */,
175
+ ) /*: void */ {
176
+ const {label, remedy, extraReservedNames} = options;
177
+ assertNameNotReserved(swiftName, reservedSwiftNames(extraReservedNames), {
178
+ label,
179
+ remedy,
180
+ });
181
+ }
182
+
183
+ // Reserved-name backstop over the resolved set. Unconditional: a plugin-shipping
184
+ // library is checked like any other, so `spm scaffold` — which knows nothing
185
+ // about plugins — cannot disagree with the autolinker about the same dep.
186
+ function assertNoReservedSwiftNames(
187
+ deps /*: ReadonlyArray<AutolinkedDep> */,
188
+ reserved /*: ReservedNames */,
189
+ ) /*: void */ {
190
+ for (const dep of deps) {
191
+ const swiftName = dep.swiftName;
192
+ if (swiftName == null) {
193
+ continue;
194
+ }
195
+ assertNameNotReserved(swiftName, reserved, {
196
+ label: `'${dep.name}'`,
197
+ remedy: `Set a different 'spm.name' in ${dep.name}'s react-native.config.js.`,
198
+ });
199
+ }
200
+ }
201
+
202
+ // Pulls apart deps that resolved to the same name by borrowing their npm scopes.
203
+ // Every scoped member of a colliding group moves: there is no non-arbitrary
204
+ // winner to keep. Exactly one pass — retrying would trade a diagnosable error
205
+ // for a name nobody can predict.
206
+ function disambiguateSharedSwiftNames(
207
+ deps /*: ReadonlyArray<AutolinkedDep> */,
208
+ autoNamed /*: ReadonlySet<string> */,
209
+ log /*: ?Log */,
210
+ ) /*: void */ {
211
+ const groups /*: Map<string, Array<{dep: AutolinkedDep, swiftName: string}>> */ =
212
+ new Map();
213
+ for (const dep of deps) {
214
+ const swiftName = dep.swiftName;
215
+ if (swiftName == null) {
216
+ continue;
217
+ }
218
+ const key = swiftName.toLowerCase();
219
+ const group = groups.get(key);
220
+ if (group == null) {
221
+ groups.set(key, [{dep, swiftName}]);
222
+ } else {
223
+ group.push({dep, swiftName});
224
+ }
225
+ }
226
+
227
+ for (const group of groups.values()) {
228
+ if (group.length < 2) {
229
+ continue;
230
+ }
231
+ for (const {dep, swiftName} of group) {
232
+ // A name we derived can borrow a second time (`AAReactNative`); the
233
+ // member whose name we did not derive is the incumbent and keeps it.
234
+ if (!autoNamed.has(dep.name)) {
235
+ continue;
236
+ }
237
+ const borrowed = scopeBorrowedName(dep.name, swiftName);
238
+ if (borrowed == null) {
239
+ continue;
240
+ }
241
+ const others = group
242
+ .filter(other => other.dep !== dep)
243
+ .map(other => `'${other.dep.name}'`)
244
+ .join(', ');
245
+ log?.(
246
+ `'${dep.name}' would share the name '${swiftName}' with ${others}, so its npm scope is prepended: '${borrowed}'. ` +
247
+ `Set 'spm.name' in ${dep.name}'s react-native.config.js to choose the name yourself.`,
248
+ );
249
+ dep.swiftName = borrowed;
250
+ }
80
251
  }
81
- return override;
82
252
  }
83
253
 
84
254
  function expandSpmDependencies(
85
255
  directDeps /*: Array<AutolinkedDep> */,
86
256
  options /*: Options */,
87
257
  ) /*: Array<AutolinkedDep> */ {
88
- const {readConfig, resolveDep} = options;
258
+ const {readConfig, resolveDep, extraReservedNames, log} = options;
259
+ const reserved = reservedSwiftNames(extraReservedNames);
89
260
  const byName /*: Map<string, AutolinkedDep> */ = new Map();
90
261
  for (const dep of directDeps) {
91
262
  byName.set(dep.name, {...dep, spmDependencies: []});
92
263
  }
264
+ const autoNamed /*: Set<string> */ = new Set();
265
+ const resolveName = (
266
+ npmName /*: string */,
267
+ config /*: ?RnConfig */,
268
+ ) /*: string */ => {
269
+ // $FlowFixMe[prop-missing] config has dynamic shape
270
+ if (config?.spm?.name == null) {
271
+ autoNamed.add(npmName);
272
+ }
273
+ return resolveSwiftName(npmName, config, reserved, log);
274
+ };
93
275
 
94
276
  const queue /*: Array<string> */ = directDeps.map(d => d.name);
95
277
  while (queue.length > 0) {
@@ -105,7 +287,7 @@ function expandSpmDependencies(
105
287
  // Resolve swiftName lazily from the same config read we already need for
106
288
  // spm.dependencies — saves a duplicate readConfig call per direct dep.
107
289
  if (current.swiftName == null) {
108
- current.swiftName = resolveSwiftName(currentName, config);
290
+ current.swiftName = resolveName(currentName, config);
109
291
  }
110
292
  // $FlowFixMe[prop-missing] config has dynamic shape
111
293
  const transitives /*: Array<string> */ = config?.spm?.dependencies ?? [];
@@ -134,7 +316,7 @@ function expandSpmDependencies(
134
316
  name: transitiveName,
135
317
  root: transitiveRoot,
136
318
  platforms: {ios: iosPlatform},
137
- swiftName: resolveSwiftName(transitiveName, transitiveConfig),
319
+ swiftName: resolveName(transitiveName, transitiveConfig),
138
320
  spmDependencies: [],
139
321
  });
140
322
  queue.push(transitiveName);
@@ -144,6 +326,14 @@ function expandSpmDependencies(
144
326
  current.spmDependencies = currentSpmDeps;
145
327
  }
146
328
 
329
+ const allDeps /*: Array<AutolinkedDep> */ = Array.from(byName.values());
330
+
331
+ disambiguateSharedSwiftNames(allDeps, autoNamed, log);
332
+
333
+ // Both checks below validate the FINAL set, after that pass: a borrowed scope
334
+ // can land on a reserved name, or on one another dep already holds.
335
+ assertNoReservedSwiftNames(allDeps, reserved);
336
+
147
337
  // Collision check: two deps mapping to the same Swift name (whether via
148
338
  // override or auto-derivation) would clobber each other in the synth
149
339
  // package layout and the centralized headers tree. Surface it now with a
@@ -154,7 +344,7 @@ function expandSpmDependencies(
154
344
  // passes but the two still collide as directories on the default
155
345
  // case-insensitive macOS filesystem (synth package layout + headers tree).
156
346
  const seen /*: Map<string, {name: string, swiftName: string}> */ = new Map();
157
- for (const dep of byName.values()) {
347
+ for (const dep of allDeps) {
158
348
  const swiftName = dep.swiftName;
159
349
  if (swiftName == null) {
160
350
  continue;
@@ -163,7 +353,7 @@ function expandSpmDependencies(
163
353
  const existing = seen.get(key);
164
354
  if (existing != null) {
165
355
  const same = existing.swiftName === swiftName;
166
- throw new Error(
356
+ throw new SpmNameCollisionError(
167
357
  `react-native autolinking: SPM Swift name collision: '${existing.name}' ('${existing.swiftName}') and '${dep.name}' ('${swiftName}') ` +
168
358
  (same
169
359
  ? `both resolve to '${swiftName}'.`
@@ -174,7 +364,7 @@ function expandSpmDependencies(
174
364
  seen.set(key, {name: dep.name, swiftName});
175
365
  }
176
366
 
177
- return Array.from(byName.values());
367
+ return allDeps;
178
368
  }
179
369
 
180
370
  // ---------------------------------------------------------------------------
@@ -188,8 +378,34 @@ function defaultReadConfig(root /*: string */) /*: ?RnConfig */ {
188
378
  }
189
379
  try {
190
380
  // $FlowFixMe[unsupported-syntax]
191
- return require(configPath);
192
- } catch {
381
+ const mod = require(configPath);
382
+ // Read both export styles, because the community CLI's two loaders
383
+ // disagree with each other: its sync path (`loadConfig`) requires the
384
+ // module and sees named exports at top level, its async path
385
+ // (`loadConfigAsync`) takes the default export only. Merging covers both,
386
+ // with named exports winning — the shape the sync path already resolves.
387
+ // Every sibling key of the default export is preserved
388
+ // (`dependency.platforms.ios` is read from this result too).
389
+ // A function-style config (`module.exports = () => ({...})`) and other
390
+ // non-objects pass through untouched — there is no default export to
391
+ // unwrap, and nulling them would hide a config that used to be read.
392
+ if (mod == null || typeof mod !== 'object') {
393
+ return mod;
394
+ }
395
+ const dflt = mod.default;
396
+ if (dflt == null || typeof dflt !== 'object') {
397
+ return mod;
398
+ }
399
+ const {default: _unused, ...named} = mod;
400
+ return {...dflt, ...named};
401
+ } catch (e) {
402
+ // A config can fail to load for reasons unrelated to SPM (it may import a
403
+ // devDependency absent in a consumer install), so this stays a warning —
404
+ // but a silent null turns a dropped `spm` block into a link error much
405
+ // later.
406
+ warn(
407
+ `Failed to load ${configPath}: ${e.message}. Any 'spm' settings in it are ignored.`,
408
+ );
193
409
  return null;
194
410
  }
195
411
  }
@@ -209,7 +425,10 @@ function defaultResolveDep(
209
425
  }
210
426
 
211
427
  module.exports = {
428
+ SpmNameCollisionError,
429
+ assertSwiftNameNotReserved,
212
430
  expandSpmDependencies,
431
+ isValidSwiftName,
213
432
  resolveSwiftName,
214
433
  defaultReadConfig,
215
434
  defaultResolveDep,