## What changed - add `NSLocationAlwaysAndWhenInUseUsageDescription` to the iOS app - localize the purpose string in Danish and English - enforce the key in mobile permission validation and App Store readiness tests ## Why App Store Connect accepted builds 1 and 2 but emitted `ITMS-90683`, stating that the final app references APIs requiring this purpose string. Shipping a corrected binary avoids submitting a candidate with a known Apple delivery warning. ## Validation - `node scripts/mobile/check-permissions.mjs` - `node scripts/mobile/validate-app-store.mjs --strict` - `vitest run tests/unit/app-store-product-readiness.spec.js` (5 tests) - Prettier check for changed JS files - `git diff --check`
47 lines
2.2 KiB
JavaScript
47 lines
2.2 KiB
JavaScript
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { cwd, exit } from "node:process";
|
|
|
|
const projectRoot = cwd();
|
|
|
|
const readText = (relativePath) => readFileSync(join(projectRoot, relativePath), "utf8");
|
|
const failures = [];
|
|
|
|
const requireText = (label, contents, expected) => {
|
|
if (!contents.includes(expected)) {
|
|
failures.push(`${label} is missing ${expected}`);
|
|
}
|
|
};
|
|
|
|
const androidManifest = readText("android/app/src/main/AndroidManifest.xml");
|
|
const androidBuild = readText("android/app/build.gradle");
|
|
const iosInfoPlist = readText("ios/App/App/Info.plist");
|
|
const iosProject = readText("ios/App/App.xcodeproj/project.pbxproj");
|
|
const iosPrivacyManifest = readText("ios/App/App/PrivacyInfo.xcprivacy");
|
|
|
|
requireText("AndroidManifest.xml", androidManifest, "android.permission.CAMERA");
|
|
requireText("AndroidManifest.xml", androidManifest, "android.permission.ACCESS_COARSE_LOCATION");
|
|
requireText("AndroidManifest.xml", androidManifest, "android.permission.ACCESS_FINE_LOCATION");
|
|
requireText("AndroidManifest.xml", androidManifest, 'android:name="android.hardware.camera"');
|
|
requireText("AndroidManifest.xml", androidManifest, 'android:name="android.hardware.location.gps"');
|
|
requireText("AndroidManifest.xml", androidManifest, 'android:required="false"');
|
|
requireText("android/app/build.gradle", androidBuild, 'applicationId "io.truckwash.twa"');
|
|
requireText("android/app/build.gradle", androidBuild, "ANDROID_KEYSTORE_FILE");
|
|
requireText("Info.plist", iosInfoPlist, "NSCameraUsageDescription");
|
|
requireText("Info.plist", iosInfoPlist, "NSLocationAlwaysAndWhenInUseUsageDescription");
|
|
requireText("Info.plist", iosInfoPlist, "NSLocationWhenInUseUsageDescription");
|
|
requireText("project.pbxproj", iosProject, "PRODUCT_BUNDLE_IDENTIFIER = io.truckwash.app;");
|
|
requireText("project.pbxproj", iosProject, "PrivacyInfo.xcprivacy in Resources");
|
|
requireText("PrivacyInfo.xcprivacy", iosPrivacyManifest, "NSPrivacyCollectedDataTypes");
|
|
requireText("PrivacyInfo.xcprivacy", iosPrivacyManifest, "NSPrivacyTracking");
|
|
|
|
if (failures.length > 0) {
|
|
console.error("Mobile permission validation failed:");
|
|
for (const failure of failures) {
|
|
console.error(`- ${failure}`);
|
|
}
|
|
exit(1);
|
|
}
|
|
|
|
console.log("Mobile permission validation passed.");
|