feat(release): enforce manifest and Info.plist release invariants

Extend release-preflight to reject an Android manifest missing the
READ_MEDIA_IMAGES/READ_MEDIA_VIDEO tools:node="remove" attributes and
an iOS Info.plist that regained dev-launcher-only keys, both
regressions introduced silently by expo prebuild during the SDK 54 to
56 upgrade.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jjbruntonandClaude Fable 5 committed 2026-08-31 16:03:04 +01:00
1 parent 7a76936829
commit ecc31a13a2
3 files changed
+46

No files matched your search

@@ -67,6 +67,38 @@ test('rejects an OTA certificate without its root archive allowlist', () => {
}
});
test('rejects an Android manifest that lost the media permission removal (expo prebuild regression)', () => {
const root = fixture();
try {
const manifest = 'apps/mobile/android/app/src/main/AndroidManifest.xml';
replace(
root,
manifest,
'<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" tools:node="remove"/>',
'<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>',
);
assert.throws(() => validate(root), /READ_MEDIA_IMAGES \(expo prebuild regresses this\)/);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
test('rejects an iOS Info.plist that regained dev-launcher-only keys (expo prebuild regression)', () => {
const root = fixture();
try {
const infoPlist = 'apps/mobile/ios/Sauci/Info.plist';
replace(
root,
infoPlist,
'</dict>\n</plist>',
'<key>NSBonjourServices</key>\n<array><string>_expo._tcp</string></array>\n</dict>\n</plist>',
);
assert.throws(() => validate(root), /dev-launcher-only keys reintroduced by expo prebuild: NSBonjourServices/);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
test('requires the local EAS plugin executable rather than its package directory', () => {
const root = mkdtempSync(join(tmpdir(), 'sauci-eas-plugin-'));
const executable = join(root, 'bin-run');