Skip to content

Commit 7af1793

Browse files
chrfalchclaude
andcommitted
fix(iOS): hard-fail the prebuild when composed headers ship the version sentinel
Enforce the stamping added above: headers-verify.js gains a --require-stamped-version gate that fails the compose verification when any ReactNativeVersion.h in the composed artifacts still contains the 1000.0.0 dev sentinel, and the workflow passes the flag whenever a version-type is set. Turns any future regression of the stamping into a red prebuild instead of silently broken community libraries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 5b17768 commit 7af1793

2 files changed

Lines changed: 60 additions & 2 deletions

File tree

.github/workflows/prebuild-ios-core.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ jobs:
212212
# privileged-consumer/Expo fixtures). Catches consumer-facing header
213213
# regressions here instead of in downstream builds.
214214
cd packages/react-native
215-
node scripts/ios-prebuild/headers-verify.js --flavor "${{ matrix.flavor }}"
215+
node scripts/ios-prebuild/headers-verify.js --flavor "${{ matrix.flavor }}" ${{ inputs.version-type != '' && '--require-stamped-version' || '' }}
216216
- name: Compress and Rename XCFramework
217217
if: steps.restore-ios-xcframework.outputs.cache-hit != 'true'
218218
run: |

packages/react-native/scripts/ios-prebuild/headers-verify.js

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
* Usage:
3636
* node scripts/ios-prebuild/headers-verify.js [--flavor Debug|Release]
3737
* [--artifacts <dir>] [--skip-compile] [--update-baseline]
38+
* [--require-stamped-version]
3839
*/
3940

4041
const {computeInventory} = require('./headers-inventory');
@@ -410,6 +411,49 @@ function runCompileGates(
410411
}
411412
}
412413

414+
// ---------------------------------------------------------------------------
415+
// Version stamp gate
416+
// ---------------------------------------------------------------------------
417+
418+
/**
419+
* Release/nightly artifacts must not ship ReactNativeVersion.h with the
420+
* 1000.0.0 dev sentinel: the compose step copies headers from the source
421+
* tree, so a compose job that forgot to run set-rn-artifacts-version.js
422+
* would silently publish a sentinel header, breaking every library that
423+
* gates code on REACT_NATIVE_VERSION_MAJOR/MINOR.
424+
*/
425+
function verifyVersionStamp(artifactsDir /*: string */) /*: void */ {
426+
const copies = [];
427+
const walk = (dir /*: string */) => {
428+
for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
429+
const name = String(entry.name);
430+
const full = path.join(dir, name);
431+
if (entry.isDirectory()) {
432+
walk(full);
433+
} else if (name === 'ReactNativeVersion.h') {
434+
copies.push(full);
435+
}
436+
}
437+
};
438+
walk(artifactsDir);
439+
if (copies.length === 0) {
440+
throw new Error(
441+
`no ReactNativeVersion.h found under ${artifactsDir} — cannot verify the version stamp.`,
442+
);
443+
}
444+
const unstamped = copies.filter(f =>
445+
/REACT_NATIVE_VERSION_MAJOR\s+1000\b/.test(fs.readFileSync(f, 'utf8')),
446+
);
447+
if (unstamped.length > 0) {
448+
throw new Error(
449+
`ReactNativeVersion.h still contains the 1000.0.0 dev sentinel — run ` +
450+
`scripts/releases/set-rn-artifacts-version.js before composing:\n ` +
451+
unstamped.join('\n '),
452+
);
453+
}
454+
log(`version stamp OK (${copies.length} copies checked).`);
455+
}
456+
413457
// ---------------------------------------------------------------------------
414458
// CLI
415459
// ---------------------------------------------------------------------------
@@ -419,11 +463,13 @@ function parseArgs(argv /*: Array<string> */) /*: {
419463
artifacts: ?string,
420464
skipCompile: boolean,
421465
updateBaseline: boolean,
466+
requireStampedVersion: boolean,
422467
} */ {
423468
let flavor = 'Debug';
424469
let artifacts /*: ?string */ = null;
425470
let skipCompile = false;
426471
let updateBaseline = false;
472+
let requireStampedVersion = false;
427473
for (let i = 0; i < argv.length; i++) {
428474
if (argv[i] === '--flavor') {
429475
flavor = argv[++i];
@@ -433,9 +479,17 @@ function parseArgs(argv /*: Array<string> */) /*: {
433479
skipCompile = true;
434480
} else if (argv[i] === '--update-baseline') {
435481
updateBaseline = true;
482+
} else if (argv[i] === '--require-stamped-version') {
483+
requireStampedVersion = true;
436484
}
437485
}
438-
return {flavor, artifacts, skipCompile, updateBaseline};
486+
return {
487+
flavor,
488+
artifacts,
489+
skipCompile,
490+
updateBaseline,
491+
requireStampedVersion,
492+
};
439493
}
440494

441495
function main(argv /*:: ?: Array<string> */) /*: void */ {
@@ -460,6 +514,10 @@ function main(argv /*:: ?: Array<string> */) /*: void */ {
460514
`(node scripts/ios-prebuild -c -f ${args.flavor}).`,
461515
);
462516
}
517+
if (args.requireStampedVersion) {
518+
verifyVersionStamp(artifactsDir);
519+
}
520+
463521
const {reactSlice, rnhHeaders} = verifyStructural(plan, artifactsDir);
464522

465523
if (args.skipCompile) {

0 commit comments

Comments
 (0)