@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
@@ -36,6 +36,7 @@ import type {
36
36
  */
37
37
 
38
38
  const {
39
+ SpmNameCollisionError,
39
40
  defaultReadConfig,
40
41
  defaultResolveDep,
41
42
  expandSpmDependencies,
@@ -43,6 +44,10 @@ const {
43
44
  const {expandSpmSourceGlobs} = require('./generate-spm-autolinking');
44
45
  const {readPodspec} = require('./read-podspec');
45
46
  const {
47
+ REACT_CODEGEN_PACKAGE_NAME,
48
+ REACT_CODEGEN_PRODUCTS,
49
+ REACT_NATIVE_PACKAGE_NAME,
50
+ REACT_NATIVE_PRODUCTS,
46
51
  SCAFFOLDER_MARKER,
47
52
  makeLogger,
48
53
  remotePackageConfig,
@@ -235,14 +240,18 @@ function translatePodspecToSpmTarget(
235
240
  // `s.dependency "RNWorklets"` — a pod-style name the `react-native-*`
236
241
  // heuristic can't recognize — to the right sibling package. Empty by default.
237
242
  podToNpm /*: Map<string, string> */ = new Map(),
243
+ // npm name → resolved Swift name for every autolinked dep, so a sibling
244
+ // reference honors that sibling's `spm.name` instead of re-deriving it.
245
+ swiftNameByNpm /*: Map<string, string> */ = new Map(),
238
246
  ) /*: SpmScaffoldSpec */ {
239
247
  const warnings = [...model.warnings];
240
248
 
241
- // Swift target name: ALWAYS toSwiftName(npm-name). The autolinker
242
- // registers each autolinked dep under that name in its aggregator (and in
243
- // any sibling spm.dependencies refs), so the scaffolded Package.swift's
244
- // product/library name must match otherwise SPM resolution fails with
245
- // a name mismatch on `.product(name: "X", package: "X")`.
249
+ // Swift target name: whatever the autolinker resolved for this dep — its
250
+ // `spm.name` override when set, else toSwiftName(npm-name). The autolinker
251
+ // registers the dep under that name in its aggregator (and in any sibling
252
+ // spm.dependencies refs), so the scaffolded Package.swift's product/library
253
+ // name must match it exactly otherwise SPM resolution fails with a name
254
+ // mismatch on `.product(name: "X", package: "X")`.
246
255
  //
247
256
  // The podspec's `header_dir` is captured separately: when it changes the
248
257
  // include surface (e.g. `<reanimated/...>` instead of `<ReactNativeReanimated/...>`),
@@ -251,10 +260,8 @@ function translatePodspecToSpmTarget(
251
260
  // `<react/renderer/components/safeareacontext/...>` resolve through
252
261
  // `-I common/cpp/`). Module-style includes that NEED the target name to
253
262
  // match (e.g. reanimated's `<reanimated/X.h>` via SwiftPM's auto-generated
254
- // module map) require an explicit `spm.name` override in
255
- // react-native.config.js handled by the existing autolinker flow, not
256
- // here.
257
- const swiftName = toSwiftName(dep.name);
263
+ // module map) are what `spm.name` is for.
264
+ const swiftName = dep.swiftName ?? toSwiftName(dep.name);
258
265
 
259
266
  // Header search paths — substitute Xcode build-setting tokens against the
260
267
  // dep root. Anything we can't substitute is dropped + warned (avoids
@@ -512,6 +519,14 @@ function translatePodspecToSpmTarget(
512
519
  );
513
520
  }
514
521
 
522
+ const siblingSwiftNames /*: {[npmName: string]: string} */ = {};
523
+ for (const npmName of siblingNames) {
524
+ const resolved = swiftNameByNpm.get(npmName);
525
+ if (resolved != null) {
526
+ siblingSwiftNames[npmName] = resolved;
527
+ }
528
+ }
529
+
515
530
  return {
516
531
  swiftName,
517
532
  sources: expandedSources,
@@ -520,6 +535,7 @@ function translatePodspecToSpmTarget(
520
535
  needsObjCPrefix,
521
536
  coreReactNative,
522
537
  siblingNames,
538
+ siblingSwiftNames,
523
539
  extraFrameworks: model.frameworks,
524
540
  weakFrameworks: model.weakFrameworks,
525
541
  compilerFlags: model.compilerFlags,
@@ -652,7 +668,8 @@ function emitScaffoldedPackageSwift(
652
668
  // the app by definition, so it stays a path reference — relative,
653
669
  // computed at scaffold time.
654
670
  const remote = ctx.remote;
655
- const rnLabel = remote != null ? remote.identity : 'ReactNative';
671
+ const rnLabel =
672
+ remote != null ? remote.identity : REACT_NATIVE_PACKAGE_NAME;
656
673
  const codegenDir = ctx.codegenPackageDir;
657
674
  if (codegenDir == null) {
658
675
  throw new Error(
@@ -670,24 +687,25 @@ function emitScaffoldedPackageSwift(
670
687
  'emitScaffoldedPackageSwift: localXcfwPackageDir is required when no remote package is configured.',
671
688
  );
672
689
  }
673
- packageDeps.push(`.package(name: "ReactNative", path: "${xcfwDir}")`);
690
+ packageDeps.push(
691
+ `.package(name: "${REACT_NATIVE_PACKAGE_NAME}", path: "${xcfwDir}")`,
692
+ );
674
693
  }
675
694
  packageDeps.push(
676
- `.package(name: "React-GeneratedCode", path: "${codegenDir}")`,
677
- );
678
- targetDeps.push(`.product(name: "ReactHeaders", package: "${rnLabel}")`);
679
- targetDeps.push(
680
- `.product(name: "ReactNativeHeaders", package: "${rnLabel}")`,
681
- );
682
- targetDeps.push(
683
- `.product(name: "ReactNativeDependenciesHeaders", package: "${rnLabel}")`,
684
- );
685
- targetDeps.push(
686
- '.product(name: "ReactAppHeaders", package: "React-GeneratedCode")',
695
+ `.package(name: "${REACT_CODEGEN_PACKAGE_NAME}", path: "${codegenDir}")`,
687
696
  );
697
+ for (const product of REACT_NATIVE_PRODUCTS) {
698
+ targetDeps.push(`.product(name: "${product}", package: "${rnLabel}")`);
699
+ }
700
+ for (const product of REACT_CODEGEN_PRODUCTS) {
701
+ targetDeps.push(
702
+ `.product(name: "${product}", package: "${REACT_CODEGEN_PACKAGE_NAME}")`,
703
+ );
704
+ }
688
705
  }
689
706
  for (const siblingName of spec.siblingNames) {
690
- const swiftSibling = toSwiftName(siblingName);
707
+ const swiftSibling =
708
+ spec.siblingSwiftNames?.[siblingName] ?? toSwiftName(siblingName);
691
709
  // The autolinker references each self-managed (scaffolded) dep through a
692
710
  // `libs/<SwiftName>` symlink, and SPM resolves a manifest's relative
693
711
  // package paths against that symlink location — so a sibling lives at
@@ -787,6 +805,10 @@ type ScaffoldContext = {
787
805
  // podspec-name → npm-name index over all autolinked deps, so pod-style
788
806
  // `s.dependency` names (e.g. "RNWorklets") wire to the right sibling.
789
807
  podToNpm?: Map<string, string>,
808
+ // npm-name → resolved Swift name over all autolinked deps, so sibling
809
+ // references honor each sibling's `spm.name`.
810
+ swiftNameByNpm?: Map<string, string>,
811
+ remote: ?{url: string, version: string, identity: string},
790
812
  };
791
813
  */
792
814
 
@@ -945,6 +967,7 @@ function scaffoldPackageSwiftForDep(
945
967
  model,
946
968
  dep,
947
969
  ctx.podToNpm ?? new Map(),
970
+ ctx.swiftNameByNpm ?? new Map(),
948
971
  );
949
972
 
950
973
  // Mixed-language fail-closed: SPM can't compile Swift + C-family in one
@@ -999,7 +1022,7 @@ function scaffoldPackageSwiftForDep(
999
1022
  .join('/');
1000
1023
  const content = emitScaffoldedPackageSwift(spec, {
1001
1024
  cacheSlotLabel: ctx.cacheSlotLabel,
1002
- remote: remotePackageConfig(ctx.appRoot),
1025
+ remote: ctx.remote,
1003
1026
  codegenPackageDir: relFromManifest('build', 'generated', 'ios'),
1004
1027
  localXcfwPackageDir: relFromManifest('build', 'xcframeworks'),
1005
1028
  });
@@ -1126,13 +1149,22 @@ function scaffoldAll(
1126
1149
  directDeps.push({name, root, platforms: {ios: iosPlatform}});
1127
1150
  }
1128
1151
 
1152
+ // Outside the try: a malformed remote config (RemoteVersionError) is a
1153
+ // misconfiguration to surface, not an expansion failure to degrade past.
1154
+ const remote = remotePackageConfig(appRoot);
1155
+
1129
1156
  let allDeps /*: Array<AutolinkedDep> */ = [];
1130
1157
  try {
1131
1158
  allDeps = expandSpmDependencies(directDeps, {
1132
1159
  readConfig: defaultReadConfig,
1133
1160
  resolveDep: defaultResolveDep,
1161
+ extraReservedNames: remote != null ? [remote.identity] : undefined,
1162
+ log,
1134
1163
  });
1135
1164
  } catch (e) {
1165
+ if (e instanceof SpmNameCollisionError) {
1166
+ throw e;
1167
+ }
1136
1168
  // A transitive-resolution failure shouldn't abort the whole scaffold pass;
1137
1169
  // fall back to the direct deps so at least those get manifests.
1138
1170
  log(`Transitive spm.dependencies expansion failed: ${e.message}`);
@@ -1167,6 +1199,13 @@ function scaffoldAll(
1167
1199
  }
1168
1200
  }
1169
1201
 
1202
+ const swiftNameByNpm /*: Map<string, string> */ = new Map();
1203
+ for (const dep of allDeps) {
1204
+ if (dep.swiftName != null) {
1205
+ swiftNameByNpm.set(dep.name, dep.swiftName);
1206
+ }
1207
+ }
1208
+
1170
1209
  const ctx /*: ScaffoldContext */ = {
1171
1210
  appRoot,
1172
1211
  projectRoot,
@@ -1175,6 +1214,8 @@ function scaffoldAll(
1175
1214
  dryRun: opts.dryRun === true,
1176
1215
  cacheSlotLabel: opts.cacheSlotLabel ?? null,
1177
1216
  podToNpm,
1217
+ swiftNameByNpm,
1218
+ remote,
1178
1219
  };
1179
1220
  const skipSet /*: Set<string> */ = new Set(opts.skipDeps ?? []);
1180
1221
 
@@ -28,13 +28,55 @@ function generateUUID(seed /*: string */) /*: string */ {
28
28
  }
29
29
 
30
30
  /**
31
- * Escapes a string for OpenStep plist format if needed.
31
+ * Escapes a string for OpenStep plist format if needed. A literal CR or tab in
32
+ * a quoted value is legal but Xcode rewrites it to its escape on the next save,
33
+ * planting a spurious diff in the user's repo — so escape those too (a plugin
34
+ * `script` with CRLF line endings is how one gets in).
32
35
  */
33
36
  function quoteIfNeeded(s /*: string */) /*: string */ {
34
37
  if (/^[a-zA-Z0-9._/]+$/.test(s)) {
35
38
  return s;
36
39
  }
37
- return `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`;
40
+ return `"${s
41
+ .replace(/\\/g, '\\\\')
42
+ .replace(/"/g, '\\"')
43
+ .replace(/\n/g, '\\n')
44
+ .replace(/\r/g, '\\r')
45
+ .replace(/\t/g, '\\t')}"`;
46
+ }
47
+
48
+ /**
49
+ * Normalize an UNTRUSTED label (a plugin's script-phase name, say) for use
50
+ * inside a pbxproj `/* … *​/` comment. Such a comment is purely cosmetic — Xcode
51
+ * regenerates it from the object's own fields — while the text AROUND it is
52
+ * scanned by delimiter, so every character that could be read as structure (or
53
+ * split the line) becomes a space, and runs of whitespace collapse:
54
+ * `findObjectByUuid` takes the first `{` after a UUID as that object's body,
55
+ * `removeArrayMembersByUuid` identifies a member line by its trailing comma, and
56
+ * `scanToClose` treats a `"` as opening a string. Stable (same input ⇒ same
57
+ * output), so re-syncing stays byte-identical. Returns '' when nothing survives;
58
+ * callers supply the fallback label.
59
+ *
60
+ * Comments RN writes to Xcode's OWN convention (`XCLocalSwiftPackageReference
61
+ * "build/xcframeworks"`, `<file> in Sources`) are NOT run through this: they are
62
+ * fixed strings, and normalizing them would rewrite bytes Xcode itself produces —
63
+ * a spurious diff in the user's repo on their next save.
64
+ */
65
+ function commentSafe(s /*: string */) /*: string */ {
66
+ return s
67
+ .replace(/[{}(),;="*/\s]/g, ' ')
68
+ .replace(/ +/g, ' ')
69
+ .trim();
70
+ }
71
+
72
+ /**
73
+ * Format the ` /* … *​/` suffix pbxproj writes after a UUID, omitted entirely for
74
+ * an empty label (an uncommented UUID is well-formed — Xcode writes those itself
75
+ * for unnamed groups). Labels that did not originate in this repo must already
76
+ * have been through `commentSafe`.
77
+ */
78
+ function uuidComment(comment /*: ?string */) /*: string */ {
79
+ return comment != null && comment !== '' ? ` /* ${comment} */` : '';
38
80
  }
39
81
 
40
82
  /**
@@ -47,11 +89,7 @@ function quoteIfNeeded(s /*: string */) /*: string */ {
47
89
  function serializeEntry(
48
90
  entry /*: {readonly uuid: string, readonly comment?: ?string, readonly fields: {readonly [string]: string}, ...} */,
49
91
  ) /*: string */ {
50
- const comment =
51
- entry.comment != null && entry.comment !== ''
52
- ? ` /* ${entry.comment} */`
53
- : '';
54
- let out = `\t\t${entry.uuid}${comment} = {`;
92
+ let out = `\t\t${entry.uuid}${uuidComment(entry.comment)} = {`;
55
93
  const fieldKeys = Object.keys(entry.fields);
56
94
  if (
57
95
  fieldKeys.length <= 3 &&
@@ -343,8 +381,7 @@ function addArrayMembers(
343
381
  const memberIndent = fieldIndent + '\t';
344
382
  const line = (
345
383
  m /*: {readonly uuid: string, readonly comment?: ?string, ...} */,
346
- ) =>
347
- `${memberIndent}${m.uuid}${m.comment != null && m.comment !== '' ? ` /* ${m.comment} */` : ''},\n`;
384
+ ) => `${memberIndent}${m.uuid}${uuidComment(m.comment)},\n`;
348
385
 
349
386
  const field = findField(text, obj, key);
350
387
  if (field != null) {
@@ -367,6 +404,38 @@ function addArrayMembers(
367
404
  return text.slice(0, obj.bodyOpen + 1) + block + text.slice(obj.bodyOpen + 1);
368
405
  }
369
406
 
407
+ /**
408
+ * Indices of the `,` separators at an array's top level — a comma inside a
409
+ * quoted member (`"$(FOO),weird"`) separates nothing.
410
+ */
411
+ function topLevelCommas(inner /*: string */) /*: Array<number> */ {
412
+ const out = [];
413
+ for (let i = 0; i < inner.length; i++) {
414
+ if (inner[i] === '"') {
415
+ i = scanString(inner, i);
416
+ } else if (inner[i] === ',') {
417
+ out.push(i);
418
+ }
419
+ }
420
+ return out;
421
+ }
422
+
423
+ /**
424
+ * The members of an array's inner text (what sits between its parens), trimmed
425
+ * and with empty slots — e.g. the one a trailing comma leaves — dropped. A bare
426
+ * scalar value parses as its own single member.
427
+ */
428
+ function arrayMembers(inner /*: string */) /*: Array<string> */ {
429
+ const members = [];
430
+ let start = 0;
431
+ for (const comma of topLevelCommas(inner)) {
432
+ members.push(inner.slice(start, comma));
433
+ start = comma + 1;
434
+ }
435
+ members.push(inner.slice(start));
436
+ return members.map(m => m.trim()).filter(m => m !== '');
437
+ }
438
+
370
439
  /**
371
440
  * Append raw string values to a `( … )` array build-setting (e.g.
372
441
  * OTHER_LDFLAGS), deduping by exact token. Creates the setting seeded with
@@ -385,31 +454,57 @@ function addArrayStringValues(
385
454
 
386
455
  const field = findField(text, obj, key);
387
456
  if (field != null) {
457
+ const isArray = field.value.trimStart().startsWith('(');
458
+ const openParen = isArray
459
+ ? field.valueStart + field.value.indexOf('(')
460
+ : -1;
461
+ const closeParen = isArray ? scanToClose(text, openParen) : -1;
462
+ const inner = isArray ? text.slice(openParen + 1, closeParen) : field.value;
388
463
  // Dedup by EXACT existing member, not substring — a substring check would
389
464
  // treat `"-ObjC"` as already present when only `"-ObjCFoo"` is there (and
390
- // vice-versa). Parse the current members (array `( … )` or bare scalar).
391
- const existingMembers = new Set(
392
- field.value
393
- .replace(/^\s*\(/, '')
394
- .replace(/\)\s*$/, '')
395
- .split(',')
396
- .map(s => s.trim())
397
- .filter(s => s.length > 0),
398
- );
465
+ // vice-versa).
466
+ const existingMembers = new Set(arrayMembers(inner));
399
467
  const fresh = values.filter(v => !existingMembers.has(v));
400
468
  if (fresh.length === 0) {
401
469
  return text;
402
470
  }
403
- if (field.value.trimStart().startsWith('(')) {
404
- // Existing array — splice fresh members before the closing `)`.
405
- const lineStart = text.lastIndexOf('\n', field.tokenEnd - 1) + 1;
406
- const lines = fresh.map(v => `${memberIndent}${v},\n`).join('');
407
- return text.slice(0, lineStart) + lines + text.slice(lineStart);
471
+ if (isArray) {
472
+ if (inner.includes('\n')) {
473
+ // Multi-line array — one member per line, before the closing `)`.
474
+ const lineStart = text.lastIndexOf('\n', closeParen) + 1;
475
+ const lines = fresh.map(v => `${memberIndent}${v},\n`).join('');
476
+ return text.slice(0, lineStart) + lines + text.slice(lineStart);
477
+ }
478
+ // One-line array (hand-edited projects, XcodeGen, Tuist) — splice the
479
+ // members in ahead of the `)`, in the separator style already there.
480
+ // Reformatting it multi-line instead would have to record the old shape
481
+ // to stay reversible on deinit.
482
+ const commas = topLevelCommas(inner);
483
+ const gapMatch =
484
+ commas.length > 0 ? /^[\t ]*/.exec(inner.slice(commas[0] + 1)) : null;
485
+ const gap = gapMatch != null ? gapMatch[0] : ' ';
486
+ const core = inner.replace(/[\t ]+$/, '');
487
+ const joined = fresh.join(`,${gap}`);
488
+ const insertion =
489
+ core === ''
490
+ ? joined
491
+ : core.endsWith(',')
492
+ ? `${gap}${joined},`
493
+ : `,${gap}${joined}`;
494
+ const at = openParen + 1 + core.length;
495
+ return text.slice(0, at) + insertion + text.slice(at);
408
496
  }
409
- // Existing scalar — promote to an array preserving the prior value.
497
+ // Existing scalar — promote to an array preserving the prior value. Skip it
498
+ // when it IS the `"$(inherited)"` the array is seeded with (emitted twice),
499
+ // or when it is empty (a bare `,` is not a valid plist element). The seed
500
+ // test unquotes first: pbxproj accepts `$(inherited)` bare, and Xcode's own
501
+ // quoted form is the same value to the build system.
502
+ const prior = field.value.trim();
503
+ const carriesPrior =
504
+ prior !== '' && prior.replace(/^"(.*)"$/s, '$1') !== '$(inherited)';
410
505
  const replacement = arrayBlock([
411
506
  '"$(inherited)"',
412
- field.value.trim(),
507
+ ...(carriesPrior ? [prior] : []),
413
508
  ...fresh,
414
509
  ]);
415
510
  return (
@@ -464,7 +559,12 @@ function setScalarField(
464
559
  // ---------------------------------------------------------------------------
465
560
  // Surgical removal — the inverse of the additive helpers above. `deinit` uses
466
561
  // these to undo exactly what injection added, leaving every other byte (incl.
467
- // user edits made after injection) untouched. All are pure string transforms.
562
+ // user edits made after injection) untouched. The exception is a scalar that
563
+ // injection promoted to an array: reversing that rewrites the whole field (see
564
+ // removeRecordedBuildSettings), so members added to it afterwards are lost.
565
+ // That is not deinit-only — every re-sync reverts from the recorded baseline
566
+ // before re-injecting, so an `spm update` discards them just the same.
567
+ // All are pure string transforms.
468
568
  // ---------------------------------------------------------------------------
469
569
 
470
570
  /**
@@ -495,7 +595,8 @@ function removeObjectByUuid(
495
595
  * `uuids` from every `( … )` list in the file (packageReferences,
496
596
  * packageProductDependencies, a Frameworks phase's `files`, buildPhases, …).
497
597
  * Only matches member lines (trailing comma), never the object-definition line
498
- * (which ends in `= {`), so it composes safely with removeObjectByUuid.
598
+ * (which ends in `= {`), so it composes safely with removeObjectByUuid — which
599
+ * holds because no comment can contain a comma (see commentSafe).
499
600
  */
500
601
  function removeArrayMembersByUuid(
501
602
  text /*: string */,
@@ -525,6 +626,33 @@ function removeField(
525
626
  return text.slice(0, f.matchStart) + text.slice(f.tokenEnd + 1);
526
627
  }
527
628
 
629
+ /**
630
+ * Drop one member from an array field's value text. The patterns mirror the
631
+ * forms addArrayStringValues inserts, tried in the order that makes the span
632
+ * removed exactly the span it added: a line of its own (multi-line array), then
633
+ * after a comma, before a comma, or alone ahead of the `)` (the one-line
634
+ * shapes). Each is anchored on a delimiter, so a value that is merely a prefix
635
+ * of a longer member is never mistaken for it.
636
+ */
637
+ function removeArrayMember(
638
+ region /*: string */,
639
+ value /*: string */,
640
+ ) /*: string */ {
641
+ const v = escapeRegExp(value);
642
+ for (const pattern of [
643
+ `\\n[\\t ]*${v},`,
644
+ `,[\\t ]*${v}(?=[\\t ]*[,)])`,
645
+ `[\\t ]*${v},[\\t ]*`,
646
+ `[\\t ]*${v}(?=[\\t ]*\\))`,
647
+ ]) {
648
+ const shorter = region.replace(new RegExp(pattern), '');
649
+ if (shorter !== region) {
650
+ return shorter;
651
+ }
652
+ }
653
+ return region;
654
+ }
655
+
528
656
  /**
529
657
  * Remove specific raw string members from an existing `( … )` array field
530
658
  * (inverse of addArrayStringValues' append branch). Leaves the field and any
@@ -542,7 +670,7 @@ function removeArrayStringValues(
542
670
  }
543
671
  let region = text.slice(f.valueStart, f.tokenEnd);
544
672
  for (const val of values) {
545
- region = region.replace(new RegExp(`\\n[\\t ]*${escapeRegExp(val)},`), '');
673
+ region = removeArrayMember(region, val);
546
674
  }
547
675
  return text.slice(0, f.valueStart) + region + text.slice(f.tokenEnd);
548
676
  }
@@ -628,6 +756,8 @@ module.exports = {
628
756
  namespacedUUID,
629
757
  serializeEntry,
630
758
  quoteIfNeeded,
759
+ commentSafe,
760
+ uuidComment,
631
761
  // Surgical-edit toolkit (in-place injection):
632
762
  scanString,
633
763
  scanToClose,
@@ -168,7 +168,6 @@ export type SpmModuleConfig = {
168
168
  name: string,
169
169
  path: string,
170
170
  exclude?: Array<string>,
171
- publicHeadersPath?: ?string,
172
171
  // Optional CocoaPods-style glob allowlist (analog of s.source_files).
173
172
  // When set, replaces auto source discovery for the module — only files
174
173
  // matching one of these patterns are passed to SPM via `sources:`.
@@ -219,6 +218,21 @@ export type PluginProductDep = {name: string, package: string};
219
218
  // in the codegen package so it compiles.
220
219
  export type PluginGeneratedSource = {path: string};
221
220
 
221
+ // A build-time shell phase for the app target — SwiftPM's missing analog of
222
+ // CocoaPods' `script_phase`. `id` is the stable ledger key and deterministic
223
+ // UUID seed (charset /^[@A-Za-z0-9_./-]+$/, so a scoped npm name works; `:` is
224
+ // excluded because the seed is `plugin:<id>`); `position` is normalized to 'end'
225
+ // by invokePlugins so consumers never re-derive the default.
226
+ export type PluginScriptPhase = {
227
+ id: string,
228
+ name: string,
229
+ script: string,
230
+ position: 'beforeCompile' | 'end',
231
+ inputPaths?: Array<string>,
232
+ outputPaths?: Array<string>,
233
+ alwaysOutOfDate?: boolean,
234
+ };
235
+
222
236
  // A plugin-declared dynamic XCFramework pair. RN validates both paths, stages
223
237
  // immutable app-local slots, and links/embeds the selected framework outside
224
238
  // SwiftPM.
@@ -302,6 +316,10 @@ export type PluginResult = {
302
316
  // for staleness, e.g. the plugin dep's own `Package.swift` and per-module
303
317
  // manifests. Folded into `.spm-sync-watch-paths` by main().
304
318
  watchPaths: Array<string>,
319
+ // Build-time shell phases for the app target, recorded to
320
+ // `.spm-plugin-script-phases.json` by main() and injected by `spm add`/
321
+ // `update`.
322
+ scriptPhases: Array<PluginScriptPhase>,
305
323
  };
306
324
 
307
325
  export type SpmAutolinkingPlugin = (context: PluginContext) => ?{
@@ -310,6 +328,11 @@ export type SpmAutolinkingPlugin = (context: PluginContext) => ?{
310
328
  generatedSources?: Array<PluginGeneratedSource>,
311
329
  flavoredFrameworks?: Array<PluginFlavoredFramework>,
312
330
  watchPaths?: Array<string>,
331
+ // `position` may be omitted here; invokePlugins normalizes it to 'end'.
332
+ scriptPhases?: Array<{
333
+ ...PluginScriptPhase,
334
+ position?: 'beforeCompile' | 'end',
335
+ }>,
313
336
  };
314
337
 
315
338
  export type DiscoveredPlugin = {
@@ -458,10 +481,13 @@ export type SpmScaffoldSpec = {
458
481
  // Bucketed dependency references — pre-computed by the translation layer.
459
482
  // `coreReactNative` is true when ANY React-* / RCT* / RCT-Folly / glog
460
483
  // dep is present (so we add React's invariant header products).
461
- // `siblingNames` are npm names that match other autolinked deps — resolved
462
- // to Swift names by the scaffold orchestrator before emit.
484
+ // `siblingNames` are npm names that match other autolinked deps.
485
+ // `siblingSwiftNames` carries each one's resolved Swift name (honoring the
486
+ // sibling's `spm.name`); a sibling absent from it falls back to
487
+ // toSwiftName(npmName) at emit time.
463
488
  coreReactNative: boolean,
464
489
  siblingNames: Array<string>,
490
+ siblingSwiftNames?: {[npmName: string]: string},
465
491
  // Extra frameworks beyond the autolinker's default UIKit/Foundation/CoreGraphics
466
492
  // set. Merged with the defaults at emit time.
467
493
  extraFrameworks: Array<string>,