Prepare App Store 1.0 build 4

This commit is contained in:
2026-08-17 16:50:30 +02:00
parent f894d43eb6
commit 0b7c938b23
19 changed files with 191 additions and 62 deletions

View File

@@ -35,6 +35,8 @@ struct App {
support_url: String,
marketing_url: String,
age_rating: String,
copyright: String,
review_notes: String,
}
#[derive(Deserialize)]
@@ -42,6 +44,7 @@ struct Release {
version: String,
build: String,
minimum_ios: String,
minimum_watchos: String,
notes: String,
}
@@ -113,9 +116,12 @@ fn verify(root: &Path) -> Result<(), Box<dyn Error>> {
if metadata.app.bundle_identifier != BUNDLE_ID {
return Err(format!("main bundle identifier must be {BUNDLE_ID}").into());
}
if metadata.app.age_rating != "4+" || metadata.release.minimum_ios != "17.0" {
if metadata.app.age_rating != "4+"
|| metadata.release.minimum_ios != "17.0"
|| metadata.release.minimum_watchos != "26.0"
{
return Err(
"release metadata must retain the reviewed 4+ rating and iOS 17.0 minimum".into(),
"release metadata must retain the reviewed 4+ rating, iOS 17.0 minimum, and watchOS 26.0 minimum".into(),
);
}
if metadata.release.build.parse::<u64>()? == 0 {
@@ -135,6 +141,8 @@ fn verify(root: &Path) -> Result<(), Box<dyn Error>> {
("keywords", metadata.app.keywords.as_str()),
("category", metadata.app.category.as_str()),
("release notes", metadata.release.notes.as_str()),
("copyright", metadata.app.copyright.as_str()),
("review notes", metadata.app.review_notes.as_str()),
] {
if value.trim().is_empty() {
return Err(format!("{name} must not be empty").into());
@@ -156,20 +164,20 @@ fn verify(root: &Path) -> Result<(), Box<dyn Error>> {
let workspace_version = workspace["workspace"]["package"]["version"]
.as_str()
.ok_or("workspace version is missing")?;
if metadata.release.version != workspace_version {
if normalized_version(&metadata.release.version)? != normalized_version(workspace_version)? {
return Err("App Store version must match the Rust workspace version".into());
}
let project = fs::read_to_string(root.join("apple/project.yml"))?;
for required in [
"PRODUCT_BUNDLE_IDENTIFIER: de.rfc1437.ironstorage\n",
"PRODUCT_BUNDLE_IDENTIFIER: de.rfc1437.ironstorage.autofill\n",
"PRODUCT_BUNDLE_IDENTIFIER: de.rfc1437.ironstorage.watch\n",
"DEVELOPMENT_TEAM: MU22FMRGK8\n",
"ITSAppUsesNonExemptEncryption: false\n",
"NSFaceIDUsageDescription:",
"NSCameraUsageDescription:",
"- target: IronStorageAutoFill\n embed: true",
"deploymentTarget: \"26.0\"\n",
"\"ARCHS[sdk=watchos*]\": \"arm64 arm64_32\"\n",
"- target: IronStorageWatch\n embed: true",
] {
if !project.contains(required) {
@@ -203,6 +211,25 @@ fn verify(root: &Path) -> Result<(), Box<dyn Error>> {
if metadata.screenshots.len() != 6 {
return Err("exactly six reviewed iPhone screenshots are required".into());
}
let iphone_size = (
metadata.screenshots[0].width,
metadata.screenshots[0].height,
);
if ![
(1242, 2688),
(1284, 2778),
(1260, 2736),
(1290, 2796),
(1320, 2868),
]
.contains(&iphone_size)
|| metadata
.screenshots
.iter()
.any(|screenshot| (screenshot.width, screenshot.height) != iphone_size)
{
return Err("iPhone screenshots must use one required 6.5-inch or 6.9-inch size".into());
}
if metadata.watch_screenshots.len() != 2 {
return Err("exactly the reviewed Watch list and detail screenshots are required".into());
}
@@ -357,6 +384,17 @@ fn require_https(name: &str, value: &str) -> Result<(), Box<dyn Error>> {
Ok(())
}
fn normalized_version(value: &str) -> Result<Vec<u64>, Box<dyn Error>> {
let mut parts = value
.split('.')
.map(str::parse)
.collect::<Result<Vec<u64>, _>>()?;
while parts.last() == Some(&0) {
parts.pop();
}
Ok(parts)
}
fn png_size(path: &Path) -> Result<(u32, u32), Box<dyn Error>> {
let bytes = fs::read(path)?;
if bytes.len() < 24 || &bytes[..8] != b"\x89PNG\r\n\x1a\n" || &bytes[12..16] != b"IHDR" {
@@ -383,6 +421,12 @@ mod tests {
assert!(metadata.release.notes.contains("Apple Watch"));
}
#[test]
fn app_store_and_rust_versions_ignore_trailing_zeroes() {
assert_eq!(normalized_version("1.0").unwrap(), vec![1]);
assert_eq!(normalized_version("1.0.0").unwrap(), vec![1]);
}
#[test]
fn repository_release_metadata_is_consistent() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"))