From 188aa070590e702b4c8bd26e3777b2e423591d94 Mon Sep 17 00:00:00 2001 From: Twyco Date: Mon, 28 Sep 2026 13:17:45 +0200 Subject: [PATCH] feat(KAN-66): prepare local iOS TestFlight signing --- native/build-ios.mjs | 509 +++++++++++++++++ native/package.json | 5 +- .../AppIcon.appiconset/AppIcon-20x20@1x.png | Bin 422 -> 638 bytes .../AppIcon.appiconset/AppIcon-20x20@2x-1.png | Bin 548 -> 774 bytes .../AppIcon.appiconset/AppIcon-20x20@2x.png | Bin 548 -> 774 bytes .../AppIcon.appiconset/AppIcon-20x20@3x.png | Bin 800 -> 1070 bytes .../AppIcon.appiconset/AppIcon-29x29@1x.png | Bin 415 -> 658 bytes .../AppIcon.appiconset/AppIcon-29x29@2x-1.png | Bin 845 -> 1086 bytes .../AppIcon.appiconset/AppIcon-29x29@2x.png | Bin 845 -> 1086 bytes .../AppIcon.appiconset/AppIcon-29x29@3x.png | Bin 1110 -> 1438 bytes .../AppIcon.appiconset/AppIcon-40x40@1x.png | Bin 548 -> 774 bytes .../AppIcon.appiconset/AppIcon-40x40@2x-1.png | Bin 1026 -> 1271 bytes .../AppIcon.appiconset/AppIcon-40x40@2x.png | Bin 1026 -> 1271 bytes .../AppIcon.appiconset/AppIcon-40x40@3x.png | Bin 1559 -> 1918 bytes .../AppIcon.appiconset/AppIcon-512@2x.png | Bin 13961 -> 14696 bytes .../AppIcon.appiconset/AppIcon-60x60@2x.png | Bin 1559 -> 1918 bytes .../AppIcon.appiconset/AppIcon-60x60@3x.png | Bin 2118 -> 2568 bytes .../AppIcon.appiconset/AppIcon-76x76@1x.png | Bin 955 -> 1243 bytes .../AppIcon.appiconset/AppIcon-76x76@2x.png | Bin 1777 -> 2192 bytes .../AppIcon-83.5x83.5@2x.png | Bin 1995 -> 2430 bytes .../gen/apple/Tests/WorkspaceTests.swift | 6 +- .../project.pbxproj | 56 +- .../gen/apple/kanbangit-native_iOS/Info.plist | 6 +- native/src-tauri/gen/apple/project.yml | 35 +- native/src-tauri/tauri.ios.conf.json | 7 +- native/tests/ios-build.test.js | 534 ++++++++++++++++++ 26 files changed, 1124 insertions(+), 34 deletions(-) create mode 100644 native/build-ios.mjs create mode 100644 native/tests/ios-build.test.js diff --git a/native/build-ios.mjs b/native/build-ios.mjs new file mode 100644 index 0000000..d12280c --- /dev/null +++ b/native/build-ios.mjs @@ -0,0 +1,509 @@ +import { Buffer } from 'node:buffer'; +import { spawnSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { + copyFileSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + realpathSync, + renameSync, + rmSync, + writeFileSync, +} from 'node:fs'; +import { homedir } from 'node:os'; +import { dirname, isAbsolute, join, relative } from 'node:path'; +import process from 'node:process'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { parseArgs } from 'node:util'; + +export const appleTeamId = 'ZZX2KZGTHU'; +export const bundleIdentifier = 'de.kanbangit.ios'; + +const plistReader = `import base64, datetime, json, plistlib, sys +def encode(value): + if isinstance(value, bytes): return base64.b64encode(value).decode() + if isinstance(value, datetime.datetime): return value.isoformat() + 'Z' + raise TypeError(type(value).__name__) +print(json.dumps(plistlib.loads(sys.stdin.buffer.read()), default=encode))`; + +function execute(command, args, options) { + const result = spawnSync(command, args, { + cwd: options.cwd, + env: options.env, + input: options.input, + encoding: 'utf8', + maxBuffer: 32 * 1024 * 1024, + stdio: options.inherit ? 'inherit' : 'pipe', + }); + if (result.error || result.status !== 0) { + throw new Error( + `${options.step} failed: ${result.error?.message || result.stderr?.trim() || `exit ${result.status ?? result.signal}`}`, + ); + } + return result.stdout?.trim() || ''; +} + +export function validateProfile(profile, mode, now = new Date()) { + const entitlements = profile.Entitlements ?? {}; + if ( + profile.TeamIdentifier?.length !== 1 || + profile.TeamIdentifier[0] !== appleTeamId || + entitlements['com.apple.developer.team-identifier'] !== appleTeamId || + entitlements['application-identifier'] !== + `${appleTeamId}.${bundleIdentifier}` + ) { + throw new Error( + `The profile must explicitly match ${bundleIdentifier} in team ${appleTeamId}.`, + ); + } + if ( + !/^[a-f\d]{8}-(?:[a-f\d]{4}-){3}[a-f\d]{12}$/i.test(profile.UUID) || + !(new Date(profile.ExpirationDate) > now) || + !profile.DeveloperCertificates?.length || + !profile.Platform?.includes('iOS') + ) { + throw new Error( + 'The iOS profile is invalid or expired. Download a current profile from your Apple account.', + ); + } + if (mode === 'testflight') { + if ( + entitlements['get-task-allow'] !== false || + entitlements['beta-reports-active'] !== true || + 'ProvisionedDevices' in profile || + profile.ProvisionsAllDevices + ) { + throw new Error( + 'TestFlight requires an App Store Connect profile, not a development, Ad Hoc or enterprise profile.', + ); + } + } else if ( + entitlements['get-task-allow'] !== true || + !profile.ProvisionedDevices?.length || + profile.ProvisionsAllDevices + ) { + throw new Error( + 'Device development requires an iOS App Development profile containing your registered device.', + ); + } + return profile; +} + +export function selectIdentity(profile, identities, mode) { + const fingerprints = profile.DeveloperCertificates.map((certificate) => + createHash('sha1') + .update(Buffer.from(certificate, 'base64')) + .digest('hex') + .toUpperCase(), + ); + const prefix = + mode === 'testflight' ? 'Apple Distribution: ' : 'Apple Development: '; + const matches = [ + ...identities.matchAll(/\) ([A-Fa-f\d]{40}) "([^"]+)"/g), + ].filter( + ([, fingerprint, name]) => + fingerprints.includes(fingerprint.toUpperCase()) && + name.startsWith(prefix), + ); + if (matches.length !== 1) { + throw new Error( + `The profile must match exactly one usable ${prefix.replace(': ', '')} certificate with its private key in your local keychain. Select a profile containing the intended certificate.`, + ); + } + return matches[0][1].toUpperCase(); +} + +export function buildIos({ + mode = 'testflight', + env = process.env, + platform = process.platform, + directory = dirname(fileURLToPath(import.meta.url)), + home = homedir(), + run = execute, + log = console.log, + now = new Date(), +} = {}) { + if (!['testflight', 'development', 'simulator'].includes(mode)) { + throw new Error('Choose --mode testflight, development or simulator.'); + } + if (platform !== 'darwin') { + throw new Error( + 'iOS builds require a Mac with Xcode, XcodeGen and the matching Rust target installed permanently.', + ); + } + if ( + env.APPLE_DEVELOPMENT_TEAM && + env.APPLE_DEVELOPMENT_TEAM !== appleTeamId + ) { + throw new Error(`APPLE_DEVELOPMENT_TEAM must be ${appleTeamId}.`); + } + if (env.TAURI_CONFIG) { + throw new Error( + 'Remove TAURI_CONFIG overrides before building the configured iOS app.', + ); + } + const config = JSON.parse( + readFileSync(join(directory, 'src-tauri/tauri.conf.json'), 'utf8'), + ); + const ios = JSON.parse( + readFileSync(join(directory, 'src-tauri/tauri.ios.conf.json'), 'utf8'), + ); + const version = config.version; + const buildNumber = ios.bundle?.iOS?.bundleVersion; + if ( + ios.identifier !== bundleIdentifier || + ios.bundle?.iOS?.developmentTeam !== appleTeamId || + config.identifier !== 'de.kanbangit.desktop' || + config.productName !== 'KanbanGit' || + !/^\d+\.\d+\.\d+$/.test(version) || + !/^[1-9]\d*$/.test(buildNumber) + ) { + throw new Error( + 'Check the iOS team, bundle identifier, version and positive integer bundleVersion in the Tauri configuration.', + ); + } + const simulator = mode === 'simulator'; + const configuration = mode === 'testflight' ? 'release' : 'debug'; + const target = simulator ? 'aarch64-apple-ios-sim' : 'aarch64-apple-ios'; + const sdk = simulator ? 'iphonesimulator' : 'iphoneos'; + const project = join(directory, 'src-tauri/gen/apple'); + const targetDirectory = join(directory, 'src-tauri/target'); + const outputRoot = join(targetDirectory, `ios-${mode}`); + const destination = join(outputRoot, `${version}-${buildNumber}`); + if (mode === 'testflight' && existsSync(destination)) { + throw new Error( + `Build output already exists: ${destination}. Move it aside, or increment bundle.iOS.bundleVersion for a new TestFlight upload.`, + ); + } + const buildEnv = Object.fromEntries( + Object.entries(env).filter( + ([key]) => + !key.startsWith('APPLE_') && + !key.startsWith('IOS_') && + !key.startsWith('TAURI_') && + ![ + 'SDKROOT', + 'CARGO_ENCODED_RUSTFLAGS', + 'RUSTFLAGS', + 'RUSTC_WRAPPER', + 'RUSTC_WORKSPACE_WRAPPER', + 'XCODE_XCCONFIG_FILE', + ].includes(key), + ), + ); + function step(label, command, args, options = {}) { + log(label); + return run(command, args, { + cwd: directory, + env: buildEnv, + step: label, + ...options, + }); + } + function parsePlist(xml) { + return JSON.parse( + step( + 'Reading local plist metadata', + 'python3', + ['-c', plistReader], + { input: xml }, + ), + ); + } + function readProfile(path) { + return validateProfile( + parsePlist( + step('Reading provisioning profile', 'security', [ + 'cms', + '-D', + '-i', + path, + ]), + ), + mode, + now, + ); + } + step( + 'Checking XcodeGen (install permanently with brew install xcodegen if missing)', + 'xcodegen', + ['--version'], + ); + step('Checking Xcode', 'xcodebuild', ['-version']); + const installedTargets = step('Checking installed Rust targets', 'rustup', [ + 'target', + 'list', + '--installed', + ]); + if (!installedTargets.split(/\s+/).includes(target)) { + throw new Error( + `Install the missing target permanently with rustup target add ${target}, then retry.`, + ); + } + const cargo = step('Selecting the rustup Cargo toolchain', 'rustup', [ + 'which', + 'cargo', + ]); + buildEnv.RUSTC = step('Selecting the matching Rust compiler', 'rustup', [ + 'which', + 'rustc', + ]); + buildEnv.PATH = `${dirname(cargo)}:${buildEnv.PATH ?? ''}`; + buildEnv.SDKROOT = step('Checking the iOS SDK', 'xcrun', [ + '--sdk', + sdk, + '--show-sdk-path', + ]); + buildEnv.IPHONEOS_DEPLOYMENT_TARGET = ios.bundle.iOS.minimumSystemVersion; + buildEnv.CARGO_TARGET_DIR = targetDirectory; + let profile; + let profilePath; + let identity; + if (!simulator) { + if ( + !env.IOS_PROVISIONING_PROFILE || + !existsSync(env.IOS_PROVISIONING_PROFILE) + ) { + throw new Error( + 'Set IOS_PROVISIONING_PROFILE to your downloaded .mobileprovision file outside the repository. Apple account setup is manual; this command never creates or downloads profiles.', + ); + } + profilePath = realpathSync(env.IOS_PROVISIONING_PROFILE); + const withinProject = relative( + realpathSync(join(directory, '..')), + profilePath, + ); + if ( + !withinProject.startsWith( + `..${process.platform === 'win32' ? '\\' : '/'}`, + ) && + !isAbsolute(withinProject) + ) { + throw new Error( + 'Keep provisioning profiles outside the repository.', + ); + } + profile = readProfile(profilePath); + identity = selectIdentity( + profile, + step('Checking local signing identities', 'security', [ + 'find-identity', + '-v', + '-p', + 'codesigning', + ]), + mode, + ); + } + mkdirSync(outputRoot, { recursive: true }); + const working = mkdtempSync(join(outputRoot, '.building-')); + let installedProfile; + try { + if (profile) { + const installedPath = join( + home, + 'Library/Developer/Xcode/UserData/Provisioning Profiles', + `${profile.UUID}.mobileprovision`, + ); + mkdirSync(dirname(installedPath), { recursive: true }); + if (existsSync(installedPath)) { + if ( + !readFileSync(installedPath).equals( + readFileSync(profilePath), + ) + ) { + throw new Error( + 'A different provisioning profile already exists with the same UUID. Resolve it in Xcode before retrying.', + ); + } + } else { + copyFileSync(profilePath, installedPath); + installedProfile = installedPath; + } + } + step('Generating the Xcode project', 'xcodegen', [ + 'generate', + '--spec', + join(project, 'project.yml'), + ]); + step( + 'Building the iOS Rust library', + cargo, + [ + 'build', + '--manifest-path', + join(directory, 'src-tauri/Cargo.toml'), + '--lib', + '--locked', + '--target', + target, + '--features', + 'custom-protocol', + ...(configuration === 'release' ? ['--release'] : []), + ], + { inherit: true }, + ); + const library = join( + targetDirectory, + target, + configuration, + 'libkanbangit_native.a', + ); + if (!existsSync(library)) { + throw new Error( + `The iOS Rust library was not produced: ${library}`, + ); + } + buildEnv.KANBANGIT_IOS_LIBRARY = library; + const archive = join(working, 'KanbanGit.xcarchive'); + step( + 'Archiving locally without Apple provisioning updates', + 'xcodebuild', + [ + '-project', + join(project, 'kanbangit-native.xcodeproj'), + '-scheme', + 'kanbangit-native_iOS', + '-configuration', + configuration, + '-sdk', + sdk, + '-destination', + `generic/platform=${simulator ? 'iOS Simulator' : 'iOS'}`, + '-archivePath', + archive, + '-derivedDataPath', + join(targetDirectory, `ios-derived-${mode}`), + 'archive', + `MARKETING_VERSION=${version}`, + `CURRENT_PROJECT_VERSION=${buildNumber}`, + `DEVELOPMENT_TEAM=${appleTeamId}`, + `PRODUCT_BUNDLE_IDENTIFIER=${bundleIdentifier}`, + 'CODE_SIGN_STYLE=Manual', + 'ARCHS=arm64', + ...(simulator + ? ['CODE_SIGNING_ALLOWED=NO', 'CODE_SIGNING_REQUIRED=NO'] + : [ + `CODE_SIGN_IDENTITY=${identity}`, + `PROVISIONING_PROFILE_SPECIFIER=${profile.UUID}`, + ]), + ], + { inherit: true }, + ); + function verifyApp(app) { + const info = parsePlist(readFileSync(join(app, 'Info.plist'))); + if ( + info.CFBundleIdentifier !== bundleIdentifier || + info.CFBundleShortVersionString !== version || + info.CFBundleVersion !== buildNumber + ) { + throw new Error( + 'The built app has an unexpected identifier, version or build number.', + ); + } + if (!simulator) { + step('Verifying the Apple signature and team', 'codesign', [ + '--verify', + '--deep', + '--strict', + '-R', + `=anchor apple generic and identifier "${bundleIdentifier}" and certificate leaf[subject.OU] = "${appleTeamId}" and certificate leaf = H"${identity}" and entitlement["application-identifier"] = "${appleTeamId}.${bundleIdentifier}" and entitlement["com.apple.developer.team-identifier"] = "${appleTeamId}" and ${mode === 'testflight' ? '! ' : ''}entitlement["get-task-allow"] exists${mode === 'testflight' ? ' and entitlement["beta-reports-active"] exists' : ''}`, + app, + ]); + if ( + readProfile(join(app, 'embedded.mobileprovision')).UUID !== + profile.UUID + ) { + throw new Error( + 'The app was signed with an unexpected provisioning profile.', + ); + } + } + } + verifyApp(join(archive, 'Products/Applications/KanbanGit.app')); + if (!simulator) { + const exportOptions = join(working, 'ExportOptions.plist'); + writeFileSync( + exportOptions, + JSON.stringify({ + method: + mode === 'testflight' + ? 'app-store-connect' + : 'debugging', + destination: 'export', + signingStyle: 'manual', + teamID: appleTeamId, + signingCertificate: identity, + provisioningProfiles: { [bundleIdentifier]: profile.UUID }, + manageAppVersionAndBuildNumber: false, + }), + ); + step('Preparing local export options', 'plutil', [ + '-convert', + 'xml1', + exportOptions, + ]); + step( + 'Exporting the signed IPA locally (no upload)', + 'xcodebuild', + [ + '-exportArchive', + '-archivePath', + archive, + '-exportPath', + join(working, 'export'), + '-exportOptionsPlist', + exportOptions, + ], + { inherit: true }, + ); + const ipa = join(working, 'export/KanbanGit.ipa'); + if (!existsSync(ipa)) { + throw new Error('Xcode did not produce the signed IPA.'); + } + const extracted = join(working, 'verify'); + step('Verifying the exported IPA contents', 'ditto', [ + '-x', + '-k', + ipa, + extracted, + ]); + verifyApp(join(extracted, 'Payload/KanbanGit.app')); + rmSync(extracted, { recursive: true, force: true }); + writeFileSync( + `${ipa}.sha256`, + `${createHash('sha256').update(readFileSync(ipa)).digest('hex')} KanbanGit.ipa\n`, + ); + } + if (mode !== 'testflight') { + rmSync(destination, { recursive: true, force: true }); + } + renameSync(working, destination); + log(`Verified local ${mode} build: ${destination}`); + return destination; + } catch (error) { + log(`Build did not complete. Diagnostic output remains in ${working}`); + throw error; + } finally { + if (installedProfile) { + rmSync(installedProfile); + } + } +} + +if ( + process.argv[1] && + import.meta.url === pathToFileURL(process.argv[1]).href +) { + try { + const { values } = parseArgs({ + options: { mode: { type: 'string', default: 'testflight' } }, + }); + buildIos({ mode: values.mode }); + } catch (error) { + console.error(error.message); + process.exitCode = 1; + } +} diff --git a/native/package.json b/native/package.json index d273e5e..ba2a080 100644 --- a/native/package.json +++ b/native/package.json @@ -14,8 +14,9 @@ "build:android": "tauri android build --debug --apk --target aarch64", "smoke:macos": "cargo test --manifest-path src-tauri/Cargo.toml --locked --features smoke-test,custom-protocol --test macos_smoke", "dev:ios": "tauri ios dev", - "build:ios:simulator": "rm -rf src-tauri/gen/apple/build/arm64-sim/KanbanGit.app && tauri ios build --debug --target aarch64-sim --no-sign --ci", - "build:ios:device": "tauri ios build --debug --target aarch64 --export-method debugging --archive-only", + "build:ios:simulator": "node build-ios.mjs --mode simulator", + "build:ios:device": "node build-ios.mjs --mode development", + "release:ios": "node build-ios.mjs --mode testflight", "open:ios": "tauri ios dev --open --no-dev-server", "prepare:ios": "xcodegen generate --spec src-tauri/gen/apple/project.yml", "test:ios": "xcodebuild -project src-tauri/gen/apple/kanbangit-native.xcodeproj -scheme kanbangit-native_iOS -configuration debug -destination \"platform=iOS Simulator,id=${IOS_SIMULATOR_ID:?Set IOS_SIMULATOR_ID to an installed simulator UUID}\" -parallel-testing-enabled NO CODE_SIGNING_ALLOWED=NO test", diff --git a/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png b/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png index 3ba6c85900771ce41d5fa553e61fc82c3e0e0db1..a17fcb8eda6caa19bd53423fb81463aaa66525ad 100644 GIT binary patch literal 638 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1SFYWcSQjyg=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G(@^*J&P+~aB5EuFNy)#gRy~NYkmHic`3^%WK z<>Z2YKq1u<*NBpo#FA923-b~Ap&ijQD1fS_xInooO4>?(cZxQ??SYi-8mXPTAKEh zubcPe@s!G~vyYz_Pu!xK_g(+yvz0gg8!4Tr%|G%h-CEFx;m4OVTbHUoUia2oIPAfm zd1rG~84kR@btjEUB4W8_oM5`AcEO1~FY9hRuHu&v`q6ktdELB-oZtYS(%gxp7=lU$0O1rcFZ=O~$!d2pSQK+3A6S z!wvIsXSgSmU|bhg1xJljoOZ4-s#H-yDZ7XrZ4*o5I)pphV1FAodx%3P%LkB*ZFRr> zV#NY%dtH`za16wyvQ-rlUwyF7>hPT*Bt?eTvLVk*gmtgU{WSLiFsA2_%&Q;v^#cdu zXPEnBV^C@FO(Ft6fb;ka#t<&@R3mSdYw+s5NHdWRheij=!W6P=Is(^6|F#+Wu!HLc zGOHTIqy+2SgnwgpAs5o$1AnW3;GYDhcmle7Xlc$E4YB(gdQ!lB@3IC#65%#%WJ+2D z7)grEZ2YKq1u<*NBpo#FA92-zpv zdFOZjd#fn=_Atw=m`}#0pO#pj$tgbVb{j>I<>h93}l7>H+ zrbqmKb>iLjbibNio_7tI;?nfE?MlNFCnp#lDG+9;DqME-R=?amv-i-=1SQ{^2^q z=YQWA=mB_{xEL%2G#OlljTnMNZBXPGVGg|Z^$|na?KuqBzdd9aNTA6j&oEOXk3pJ4 z1mQrCDO<4dy&8-~FOPJFz{$igs)0VpjFY6pSJp91$`{NIaEM(>9VBnA!U=VguVPN85p^gLJ?!LqD`SJ^@gb2ukzb^sJ0#*;A zz8chV;EzWiz<;ztkqw5g@3kFZ;FJ?!kc=@v5r4kq0>gKpKBX!LjM#Y4cLZ)GRU8P5 zR$`)r5P+FMY*HB>nPk*~qYfN(;HU#f9XRU1Q3sAXaL@uKVw>r(#wMizW(LemLV!`} zXxx87ie7KNLA547e0W1wf_hDUVOJG+)1NAxCSVrnK#<#*g2ikYG`M63T{MH-1acd=pq`~sz&M!FBFHTu zH^FNX&{*(R{bt6&5RxD_fZT#Ih>oma?JLkU#F_smKHX#hj=VC`E5tw<0+i-J(TWTJ Y7ZtbZf)9KA=Kufz07*qoM6N<$g7uW}l>h($ diff --git a/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png b/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png index e3f34d5eb44d641d3bde798c7e319270d2603583..33054874e7bcccf8d1491d22bb7641f5f2b7d75f 100644 GIT binary patch literal 774 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1SFZ~=vx6Pg=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G(@^*J&P+~aB5EuFNy)#gRy~NYkmHic`3^%WK z<>Z2YKq1u<*NBpo#FA92-zpv zdFOZjd#fn=_Atw=m`}#0pO#pj$tgbVb{j>I<>h93}l7>H+ zrbqmKb>iLjbibNio_7tI;?nfE?MlNFCnp#lDG+9;DqME-R=?amv-i-=1SQ{^2^q z=YQWA=mB_{xEL%2G#OlljTnMNZBXPGVGg|Z^$|na?KuqBzdd9aNTA6j&oEOXk3pJ4 z1mQrCDO<4dy&8-~FOPJFz{$igs)0VpjFY6pSJp91$`{NIaEM(>9VBnA!U=VguVPN85p^gLJ?!LqD`SJ^@gb2ukzb^sJ0#*;A zz8chV;EzWiz<;ztkqw5g@3kFZ;FJ?!kc=@v5r4kq0>gKpKBX!LjM#Y4cLZ)GRU8P5 zR$`)r5P+FMY*HB>nPk*~qYfN(;HU#f9XRU1Q3sAXaL@uKVw>r(#wMizW(LemLV!`} zXxx87ie7KNLA547e0W1wf_hDUVOJG+)1NAxCSVrnK#<#*g2ikYG`M63T{MH-1acd=pq`~sz&M!FBFHTu zH^FNX&{*(R{bt6&5RxD_fZT#Ih>oma?JLkU#F_smKHX#hj=VC`E5tw<0+i-J(TWTJ Y7ZtbZf)9KA=Kufz07*qoM6N<$g7uW}l>h($ diff --git a/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png b/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png index 8bcff760919c7cb9fb81bfaae5c1886733352a05..62c3ed2ac98117681c1351a57bc8a325bc12c744 100644 GIT binary patch literal 1070 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw1SGf4^HT*<3dtTpz6=aiY77hwEes65fI(Mj+uemhiQyH=O_E(%T+`QVA zlMDUU;b+`GVnOU!6D0_P<%b^hxM~gTDUm9iNZ4zGJjo)^BBU$@u1xBe&kTX}TW`_3!xF zAGg!CV(0ycsGz44uUux;biIAK!Q%d3l}`r_@2HzRE#&99=f}59YHZ;P|J;4f{f6{I zy>4k{2d}u=9iMo%3o`huI~#RJ!g%}V!YXS<29Kbp&u15Jsr8r{GoQ6#miPDd`zK%8 zlIk(B?@8aTXHsADx=U0pPcvlfmPnbaJ#zxDYI}zDjI_f1pN~A9pU&GCVUVUSlOuYy zDd?6~*00lDRa@!2IRJ zrGD!hTJP%5GBPAd`kSn)9_+rlh;yoFs8`x4RUolvuWw7Glh#@>kvkt;nwump@*lX=GyU>x&DFb? zdnR+e-~U*bW2xum#h;!}OqWxf%KFv*d*o8xR^43h+n2-+Oxw0JH~Zy%o;ne?04z%;qmdKI;Vst04?g(FaQ7m delta 788 zcmV+v1MB>*2%rX#8Gi-<0027t*>V5?0_RCYK~#9!?c2{!(_k0}@TaxVWikodP$Qzo zOofD)AR0a3jqT(`JbCeCj7Lt!@D~`p49_MW^&&gyX?PP8jJksuHw_2bl%Qi1+>dN9 zSNn9n%?$09HCfr-KHr2igtGS2_kH^kp7)g$HDHqY^dc;6CV${lAqnx~Ds*x#c}8K# z<%f`O0;as#h~fV$|L=I4Lp~OC_G|WqS^QG?B`+) zj9vNhT2uheGk=$g)WG3XM9Ec)ynD47Cz_3cw2+N)-?}Ud*>{T6t#hrVRxWLT1P;oZ z-=76XfiVPbHQPV(P}wR$Sv?x1TB^XG!Z!5y+zmfxCsTxd`+Grj8Q+q8s1Kxm7xb#f zJpm&m^Ku1pAJ%PcA{HJ(N?t|msKWv?aQTF3k3wv74_3gU6>$9v%Ia9MlKN|zy(DQ`A~1moOke^Nn7{-kFo6k7U;-1E zzyu~Rfqw~1VE({z-9NywB4AhQuO(Y7s#yYK6@ewctk!yjTFC{PR4Z5&qERKo>>Mny zXxB2hhgKq-e0@W)@{8%`3CJz4?@71Bx>BR^PFuz>yP(}@#5SuxwWWo@X$5f>yaD`j zpZ3$VMBomHc=5m)#VJ^{inwgvDM70k^qzOjT7OHliXrbgI3oMF@78EUtG32tW_X(W zR%S*GeC^;Q1P8{sZDq8f@xN6(4^G_@M%=^Pq0xl4UCWkm*T>VbS%IgPMhlt@xAD{m zH$;R}^U7K|BXsKv3dbz##!Yvofxa6grY)PtmE^d%y3_xtzI(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!tD$=lt9L5blgLtNz7_s&2O_7YEDSN2z>gk{ zm6Hqp0fkgcTq8G(q^ z8glbfGSez?Ygpv=ybh>A18ze}W^QV6Nn&mRLXSm=p`n$bv6YE2#FBrf>xCH@7>hhz z978;gC;$2X-=3NI@MZaYHM@HSZ(lEdeP7OK0&B(09d}xKKc_M;v2>9Dg8%>j?|*%F z=cee*_Wyso&Fjoqc3sPrz5Av?+}}?IuZpZgd0M6SUR^MArgq@xN7JjX7)@zxe0cr; zefj@&sda*iF0Rgs4^DpH?h|4TlK8woUnfHK-`^jf-(H)2U%uYJz~ImS&+pG4mtS)z zMquUr{QVn06dPV^Ej`UKuvwJJ&bEZ#Wd_uFgIY;0^x zwb(hn|9!~*p5MNq`2AK{fi0mx)KI+tZq?z%>Wyg}(>}CT%&%w&we@|Jd*x1GvHMxo zYcXMUe3N<(teO}2GDBvTKTe?@i5a&1&`l640+a*J5!c>3Ibz{!PC{xWt~$(6995H2eSYG delta 400 zcmV;B0dM}21)l?u8Gi-<0035#XAb}X0cJ@=K~#9!WBCsR3_$So#~X%;k2f$}`}&CC z`nQJ+)B?OrTnui)#tiwg0SwX{B49B_kfpocont7wHJjn{zi$k*0wBw4m7*Df#cUWD zuYP&>Kk9NjO)W&Wc&kA(12b2mX8px4_ZS)I0Dk@d&G7N>*MI+vN=M`VGtd!$e9OW> zX8?*uyq5m^@rU8ttv3w6KmGvo|Nr^N@CV3d73PAg`TZ7X2tNk{BMTFl&kAI7sfjQ! zao{$Z1)qfv`i_w8BZVRxoEGD>6y!q?P^xl(i@iVm0F05v?zJ6Y5VTbQV`MRqejYsu z(kz7qDY*cjet)_rq)|&pEgiLV)Kbzd1=SXm0DSs!R^qg*W$?Bf=w6?K07xUQ>WLD- z#LLV@-!>A^J|=@-4_7kbO*`rhctrG|eprnGdoDWFI4PUuMFS ub^mw2J9zjS_TSIkPl&f8329dp^00{s|MNUMnLSTZk1h$L- diff --git a/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png b/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png index b9cd3140c29661928ef9fc8bd895834e0fa1b29c..e73794df4e0f239c13d5735bfc1f2e9dc5ef5f9a 100644 GIT binary patch literal 1086 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 delta 1100 zcmV-S1he~|3)Tpb8Gi-<0042w*=zs+1RY64K~#9!?VU?Z6Hyd~PlrOwOB)La7{C}5 zFvOUUsEN3fuyMmp(T%znH=r&6H*NrTLLx>CF~o`?Dj*NbBLzxn zp-|@x(3F8P({{#A;s2ASnS^Q1{W$mBJ2&~~m;<6nrM@kXQGd_;0DW=|OV=Nl9CXli zDYH!azF;e%s$&&&HoZ{wy(T5NOMVZvOm)%AiEdij@X%q`2}>5$Mh2I&wfWa8>X`WeiVCXBzCw>r-%+V%rUtXn@;~A>&u@>4A7cN7Zdp|-eqb|c{R%(%r>{p8;`lm!9)2Y6jnmoRgE(ExM3%C&A0xsY}fPV|PfC~XG-~uiLxPS||KmfRa3jr?R z0xkr&fD5<~-~uk-LVydnfC~XG-~z6W^2;1EMw8z0Y3hlPyJ*;s#I6}5isO>orPR29 z;+nasqNg_0y2N%Q$%kfB@S%lx$!N=8HWgx>qJ4a5B=eIARs|oLYqt+ARR=Gy=5Xy* z#eekY#phiaycXdUWaC~m-Y{R3PDqa1@b*+TDYKV0k;L)pVXoF?L1U9L2Rqj@vZF*8 zOnoYIdU7S+MLVC-WbIEnB$)3xerb@m`c+M05}RUwrd8XCLaYic%oYSk;~Uo{o10CVg{&yvMG97q8G78%13Y zO0m~WyVx-akT;?7K6ZP%MX@yaBzgB#v2zDEn4qoK^&f`Qt?jb{ex!}0d+I-Od$3`d S>L_~v0000Z2YKq1u<*NBpo#FA92-zpv zdFOZjd#fn=_Atw=m`}#0pO#pj$tgbVb{j>I<>h93}l7>H+ zrbqmKb>iLjbibNio_7tI;?nfE?MlNFCnp#lDG+9;DqME-R=?amv-i-=1SQ{^2^q z=YQWA=mB_{xEL%2G#OlljTnMNZBXPGVGg|Z^$|na?KuqBzdd9aNTA6j&oEOXk3pJ4 z1mQrCDO<4dy&8-~FOPJFz{$igs)0VpjFY6pSJp91$`{NIaEM(>9VBnA!U=VguVPN85p^gLJ?!LqD`SJ^@gb2ukzb^sJ0#*;A zz8chV;EzWiz<;ztkqw5g@3kFZ;FJ?!kc=@v5r4kq0>gKpKBX!LjM#Y4cLZ)GRU8P5 zR$`)r5P+FMY*HB>nPk*~qYfN(;HU#f9XRU1Q3sAXaL@uKVw>r(#wMizW(LemLV!`} zXxx87ie7KNLA547e0W1wf_hDUVOJG+)1NAxCSVrnK#<#*g2ikYG`M63T{MH-1acd=pq`~sz&M!FBFHTu zH^FNX&{*(R{bt6&5RxD_fZT#Ih>oma?JLkU#F_smKHX#hj=VC`E5tw<0+i-J(TWTJ Y7ZtbZf)9KA=Kufz07*qoM6N<$g7uW}l>h($ diff --git a/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png b/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png index 51b1c2a0ec96d058b242ba09f0689bb9fa560ef7..bca916d44577032a2434ec10fc70fef114d33dff 100644 GIT binary patch literal 1271 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51SA=YQ-1*|g=CK)Uj~LMH3o);76t~8P{Rub zhEf9thF1v;3|2E37{m+a>1WvH8Kk^w6HQYu`)K-HZZU9Zu?R6Vv@$fdGBJi&^6zxLFarbgbx#+^ zkcwMx=Xm=_Im)!RH-3mv5Lmf@!#n4eOON5PX7Sf+7lw=G&r&n9eSJ2#dezH?=6i3w zSo+QFi&j;JQ}qV5WgFern1*WX`_Orysl}Z?J1ongM65hz#(NX#G_(Er>0;B~*ejoY zTC~8Td*auhms9IaBu*C8=`qM@`5jA~VC-jmJNoyU$kX>O1cwD!%zCEw{n@gzJr{M_ zZ$CZvLecof1F3D_?}Xi`-ej}MFtBsgPm>jLdo$mZU)?fYK<@58o2}OE*10z&HkHTE ze)Ye_y{`U?%&&rW9piVm*75ttbGJ{(iDqbce!*O9&Q85IwvlR=rls4Myta+pC)BrW z=fqylJ1gH!zqt0L_QR8RcY6Kz(>rx--6xZ3rr z(QIFWwrHPX_{=*(Q(c#vyf)$6xU@YfTeoy=sgkgVPwQWuz(lQ>pdFnXAD>f@_3e?B z65)9#aVast;KIt%2YrgNeruU`osHd*?9qPv4(sf#YvSTWS4Zim&)TZP!oYKFcR~9^ zrZ{0HU@ni32vxgN`A&TGDU&nbVz^$5p1FOD@y^6|;*K9a9XuR**)HYvvAM7INo*?R zmadAi&E(x|yZ4#jotvlp*YBJhf5zszxt=8Vxt{j4!gd{L=WXw5q62cWXO&0KFVpWU zTrS!gnp$<+BA_Z;s&V6kGgs!B&sw?XE#sG>v<>%4bCbNeKd(93Ja5B!d488mcdWjP tZ}$$IC9&J_pv6|c?K@vTd%N@>qn=PwR`eZ@1;Ao~!PC{xWt~$(69C5qBt`%L delta 1015 zcmV>1IbB5K~#9!?VUYL6JZ#~pSv~%N}Cc9kgpOF zO~epG99)#m;L3*^EEs3lm~@gjxaq2)i?|V80S6OY(1aK@fJ%f`C?8Ux^aE&G>OB`) zuC=yz$4M^j|Ccmvn!EJvuh08&m*;(IO-hgoW`m0~y)#D(A%7nQ6H!`-da0Obv$v4T zVy8Bzn|f<*(v?cLs`G?=0z>gAjjd0TXKji?M^P#zoT8lu8@p(@xksPCso!%n^6gp4 ziOZ(3>^$9X?WeAD*VGFfU3p0!|0IDEhMK!`M&n-sN4`I&iC;4?mV>^UPI`RlUdDSO zt)#vE39nOZMUi zf)yH05rIiXJAEI00Ji$)4Ojl8pf+s17)64B6ZYhrmyiQHDxb>VDz)weE^>v~hc^L{*fn|{sX zRQ*6l-hbnFW>(}!iiZ9UDsQREdrt8EDh1vwWwh0(@zAgc%$_jR$U7^zEn?Y8ti-m$ z<{SgfOj(^H{rYe-jCCLu(hvwB5CS0(0wEAUAOu1n1OfO2zN;%6S_%faygHA+-*(2)l1o( z|9?kgv5bL00D%T0UbZ)*?1@g;oz-Qv!-P)Qo!C~{0uwr6cVb^{CkpTAgx!f;t=4I= zV;s~7J7ah9rQBeB7mR2mbjJ#5I5HcP|04e+tER lV}P%1`SOgfFx37X#vgTDj!WD(Fq{AY002ovPDHLkV1gdx*t!4! diff --git a/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png b/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png index 51b1c2a0ec96d058b242ba09f0689bb9fa560ef7..bca916d44577032a2434ec10fc70fef114d33dff 100644 GIT binary patch literal 1271 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51SA=YQ-1*|g=CK)Uj~LMH3o);76t~8P{Rub zhEf9thF1v;3|2E37{m+a>1WvH8Kk^w6HQYu`)K-HZZU9Zu?R6Vv@$fdGBJi&^6zxLFarbgbx#+^ zkcwMx=Xm=_Im)!RH-3mv5Lmf@!#n4eOON5PX7Sf+7lw=G&r&n9eSJ2#dezH?=6i3w zSo+QFi&j;JQ}qV5WgFern1*WX`_Orysl}Z?J1ongM65hz#(NX#G_(Er>0;B~*ejoY zTC~8Td*auhms9IaBu*C8=`qM@`5jA~VC-jmJNoyU$kX>O1cwD!%zCEw{n@gzJr{M_ zZ$CZvLecof1F3D_?}Xi`-ej}MFtBsgPm>jLdo$mZU)?fYK<@58o2}OE*10z&HkHTE ze)Ye_y{`U?%&&rW9piVm*75ttbGJ{(iDqbce!*O9&Q85IwvlR=rls4Myta+pC)BrW z=fqylJ1gH!zqt0L_QR8RcY6Kz(>rx--6xZ3rr z(QIFWwrHPX_{=*(Q(c#vyf)$6xU@YfTeoy=sgkgVPwQWuz(lQ>pdFnXAD>f@_3e?B z65)9#aVast;KIt%2YrgNeruU`osHd*?9qPv4(sf#YvSTWS4Zim&)TZP!oYKFcR~9^ zrZ{0HU@ni32vxgN`A&TGDU&nbVz^$5p1FOD@y^6|;*K9a9XuR**)HYvvAM7INo*?R zmadAi&E(x|yZ4#jotvlp*YBJhf5zszxt=8Vxt{j4!gd{L=WXw5q62cWXO&0KFVpWU zTrS!gnp$<+BA_Z;s&V6kGgs!B&sw?XE#sG>v<>%4bCbNeKd(93Ja5B!d488mcdWjP tZ}$$IC9&J_pv6|c?K@vTd%N@>qn=PwR`eZ@1;Ao~!PC{xWt~$(69C5qBt`%L delta 1015 zcmV>1IbB5K~#9!?VUYL6JZ#~pSv~%N}Cc9kgpOF zO~epG99)#m;L3*^EEs3lm~@gjxaq2)i?|V80S6OY(1aK@fJ%f`C?8Ux^aE&G>OB`) zuC=yz$4M^j|Ccmvn!EJvuh08&m*;(IO-hgoW`m0~y)#D(A%7nQ6H!`-da0Obv$v4T zVy8Bzn|f<*(v?cLs`G?=0z>gAjjd0TXKji?M^P#zoT8lu8@p(@xksPCso!%n^6gp4 ziOZ(3>^$9X?WeAD*VGFfU3p0!|0IDEhMK!`M&n-sN4`I&iC;4?mV>^UPI`RlUdDSO zt)#vE39nOZMUi zf)yH05rIiXJAEI00Ji$)4Ojl8pf+s17)64B6ZYhrmyiQHDxb>VDz)weE^>v~hc^L{*fn|{sX zRQ*6l-hbnFW>(}!iiZ9UDsQREdrt8EDh1vwWwh0(@zAgc%$_jR$U7^zEn?Y8ti-m$ z<{SgfOj(^H{rYe-jCCLu(hvwB5CS0(0wEAUAOu1n1OfO2zN;%6S_%faygHA+-*(2)l1o( z|9?kgv5bL00D%T0UbZ)*?1@g;oz-Qv!-P)Qo!C~{0uwr6cVb^{CkpTAgx!f;t=4I= zV;s~7J7ah9rQBeB7mR2mbjJ#5I5HcP|04e+tER lV}P%1`SOgfFx37X#vgTDj!WD(Fq{AY002ovPDHLkV1gdx*t!4! diff --git a/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png b/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png index 30decc9810847f3ffbe6f2a75f833389e4b5d58c..a9a36ffe28d3870018d52e25d27068ca15e35bb4 100644 GIT binary patch literal 1918 zcmcgtc{tnI8vf~GiM?YhwROtaLZYa~C{nRiG=f@^5sFBtts-cP);2uQ&vWlHf82lXALqR1dC&7c-*?_~&i7?t+#IDL zst^DGq@A7Yu^=Y=dx#$a=NF&{k?%#tOqE|0D+5@M7F2LV+W91^qh*6x;C^3P=DM@L=#DeHUkkX-`d?>+zln_MF z^$-vMQ<$j*6m}kJYT*gDM3|c)&Ym-b!4NRmfEwY=-vkj+!DM3M{}!+u=8{2y-hU!c z$Ppp2l)#AS|7t8f;cx`}EW+&U*C2QMm6V+Xo8OlsKY|yiXk0wNN^_6OTn56&bO^%>Q-dgFmvl?Yp%q$s|^-aQs7%p*A z=C=t9cLGmISmhy;)2NWvige*A>z$3j@U7RZQ2z@W$;qD&68mb?awc@$a!$?f0^F9N z6z#Kauk>Tz8If-x$pvvmPgAgG#cNd@0Nw24Ojr23x%XSN> zFo)UAtUa0>@nP<#A~%l{X`YtKA+rg1rg#iW53*rq+&~6t#&KByrsvib^|K;*~MZq8`EWF9# zQfiF}N9RSN_W2#x6ZTNVkp24G#inH=#_()f&PWLK#+8(XVHe_#qgWk>$S8z!q`p(^ zd~+B|EMS_C_>S12{A;!cG$JgK;$QS4h2h>wBJKpr&MNjT^-0?XvL$~m`)Sk5T_NFS zqt6~6m-<7K^f@BKka$%XI(ls`X5?SxrC*Rg+3IW!nu8#2qBC3i#bZveJ=+Yp&!nW9HfNAIxi*#6Dm z|1VvdRRk$j814TdPmtA6Qo$EL*A8mIW z`eZAi?umu>EZc9EU7A1XGMJLPIAE^bs&7S#ds(~QrlZrLC}U7gakWc)<_8+v%n7rZ zsx?mXw#1@pz^hKI;Kpmx<*wRHao_dZiIXL$=H$P|#cT_hURdIaIO%k0Ai4q5xQstH z;_F@G{dh7a2TEY_Dhc7u^^I+ME@^|_e1W^f-pSl%n*c<^;B?>9FM8`uUgw{yE+0eZupyI4R3Nx%79%AQW!KSw-Ew_)vteeX^iw zSe+ZmgU>His~2)r*4G&X59*%?<9Pgf>~>L&YI5(*o-Ndluz^&Tv9~&XE@8a_^1@;H zZVG9oFsV9TBrA#)Pow5J1hC>OC$BwTD>e&Q&7l%1)T`UU5_IC}wt=yQLROamH@DM0 z*zGfmen|a*=-_pu*Xf=(-SiAy8dzFal%nj{;QZG@QJcB82~EVJ**b&O2>hDSV{2s~UcEmA%*^|9B c*_QSN7|pQi;Lfa_2D=mBjB>MYKnA4#3nUn5hyVZp delta 1553 zcmV+s2JZR(4wnp&8Gi-<0022;Hqrn91>Q+SK~#9!?VU?Z8&?#D&rBw@V{l0dArGFa zhy-cUE~-AHhFx2>X~L?DE>P2`n?SM# z8$!TtaBW;WF`nKt60SqXHe(yxbNzo3!a}k&_k6zRo_lA+(SLLS6~V&x3eErRC2!PE ztMMTHvAs-@RE&Ui+^UOSs_UQ@XA||*x6vEV_t4q8_M$#hDMT=f{Azugrb9o`Y9a_T z2S|&fi7q!^p#I~%hWG;&%wk9UQ#8KmLF6mIWg+=`+eJ&F_bqtDKS`rPE~q@$gyex! zedN|$#S7*&c7O1}7vzmCBeGRQkE?@*&U~16jm{}}`mdj9@cwNSO;uc!-8p|Vr{t`H z$2VqZaPc;xXak12t{8>bz6JBl{n$5x$k~W*U-{H@Cab1v?tRQ$yYc?L&x~D8PQj66 z%qXa^=GqT-cGWkUTkx=dQb)CHU)bHq%A{^8n5XyG4}Yf>gvs8F2BwtERGk*gsTs(@ za9~1zMlc(ni+B+kNtoD}P0wO=uW=v;V;delE%S)YD;c9nSWn#_O6C%t9W|EK&G}KuJG08GTZc&)E^z_jD^>(0l(`-b%l2 zc)gX5UFbBvFf}8XXX>M|-;G%-JNyNX{TZYa{eR~wGMm*(%-mX$y9z)!2p5*1Oo^L z5DXv~KrjRY2nG-gAQ(U}fM5W@5DXv~Kz}fRU;x1Yf&m0WFo0kH!2p5*1Oo^L5DXv~ zf&l~r2&Vi!4rHYo##Yw<_-@v#fE!p;qTNh6+6S36=O}mYq_PUm)(Iu_{i0Nojiwx8 zOjcqbMT$O>rF%)_w{_#kYX638Uy7*ccEbG-sRRu}Sd`UkO62IvaKvH<7h4JD^ncXw zT5HzcNr@6Qtl*x5h4vjhsB2<)>{6#_hWi;EE_0fy26)U?dpX%sFsD0i)L2WBd8|C< zO0*ABUYz|vuULCo(P=DKt2-Isw9GTNrrOJ9b_e5Le%$UYS!{!7vFB6{m8e1VPRi(c zR7`_tz78&^YPLaiW-uxp1j0!cfPY{B!2p5*1OrRE755>RjxfjxyH(HDwILFc!fw?T zXA>eJDePA5adj#Pg`BWk?ahX6L_$*7tyqKG>9nH}g_dNnQ+8{d%D?-^4{sr zxtmyzZ3a6tJ9bvh52@Q}sOt)%W)s*IJ2OMk1=-#>ANJ8z--sUBiD5=pX1E0~ESWAx?Pclu~x3X-d2E3~&-E=uq&tMMQW z2PX81jai7M%A8Q~A%>NE_?84`iXPkW=wq86T1$i=n-2D_5Z^f9v*kh_&L~Lm-Zgqo z6l31llFs56w*7>ntni|VAG2YRe6ybK=jJM)_QSsgB5a#p3@8Nv015yANkvXXu0mjf D%Q*Dl diff --git a/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png b/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png index beb2f50d75ea0291402730fd4b1caba933e5f031..bf7966eb924ee3c6bfe8ccb6b7197e4a90fc32b3 100644 GIT binary patch literal 14696 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 Zf|DJ^d{Kak6WAVPbny6rtbKMj{{tyPc&h*a literal 13961 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 b*3?5_B;BH&JH2`u>`s6q+Qqis#xMC#ThVA* delta 1553 zcmV+s2JZR(4wnp&8Gi-<0022;Hqrn91>Q+SK~#9!?VU?Z8&?#D&rBw@V{l0dArGFa zhy-cUE~-AHhFx2>X~L?DE>P2`n?SM# z8$!TtaBW;WF`nKt60SqXHe(yxbNzo3!a}k&_k6zRo_lA+(SLLS6~V&x3eErRC2!PE ztMMTHvAs-@RE&Ui+^UOSs_UQ@XA||*x6vEV_t4q8_M$#hDMT=f{Azugrb9o`Y9a_T z2S|&fi7q!^p#I~%hWG;&%wk9UQ#8KmLF6mIWg+=`+eJ&F_bqtDKS`rPE~q@$gyex! zedN|$#S7*&c7O1}7vzmCBeGRQkE?@*&U~16jm{}}`mdj9@cwNSO;uc!-8p|Vr{t`H z$2VqZaPc;xXak12t{8>bz6JBl{n$5x$k~W*U-{H@Cab1v?tRQ$yYc?L&x~D8PQj66 z%qXa^=GqT-cGWkUTkx=dQb)CHU)bHq%A{^8n5XyG4}Yf>gvs8F2BwtERGk*gsTs(@ za9~1zMlc(ni+B+kNtoD}P0wO=uW=v;V;delE%S)YD;c9nSWn#_O6C%t9W|EK&G}KuJG08GTZc&)E^z_jD^>(0l(`-b%l2 zc)gX5UFbBvFf}8XXX>M|-;G%-JNyNX{TZYa{eR~wGMm*(%-mX$y9z)!2p5*1Oo^L z5DXv~KrjRY2nG-gAQ(U}fM5W@5DXv~Kz}fRU;x1Yf&m0WFo0kH!2p5*1Oo^L5DXv~ zf&l~r2&Vi!4rHYo##Yw<_-@v#fE!p;qTNh6+6S36=O}mYq_PUm)(Iu_{i0Nojiwx8 zOjcqbMT$O>rF%)_w{_#kYX638Uy7*ccEbG-sRRu}Sd`UkO62IvaKvH<7h4JD^ncXw zT5HzcNr@6Qtl*x5h4vjhsB2<)>{6#_hWi;EE_0fy26)U?dpX%sFsD0i)L2WBd8|C< zO0*ABUYz|vuULCo(P=DKt2-Isw9GTNrrOJ9b_e5Le%$UYS!{!7vFB6{m8e1VPRi(c zR7`_tz78&^YPLaiW-uxp1j0!cfPY{B!2p5*1OrRE755>RjxfjxyH(HDwILFc!fw?T zXA>eJDePA5adj#Pg`BWk?ahX6L_$*7tyqKG>9nH}g_dNnQ+8{d%D?-^4{sr zxtmyzZ3a6tJ9bvh52@Q}sOt)%W)s*IJ2OMk1=-#>ANJ8z--sUBiD5=pX1E0~ESWAx?Pclu~x3X-d2E3~&-E=uq&tMMQW z2PX81jai7M%A8Q~A%>NE_?84`iXPkW=wq86T1$i=n-2D_5Z^f9v*kh_&L~Lm-Zgqo z6l31llFs56w*7>ntni|VAG2YRe6ybK=jJM)_QSsgB5a#p3@8Nv015yANkvXXu0mjf D%Q*Dl diff --git a/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png b/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png index cb0c0b2c327b93859b1c8dc2182c0b9dce24a1b0..c75d20429c689f27c75ef5c9811d370fb7d36906 100644 GIT binary patch literal 2568 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 literal 2118 zcmbuBdpr|*AIGPOajb-Li`5j7%iOxCP3vfgrsOV!GLq5UEly*^F_cQgv@*KLLPPEj zYBDof2Xj3lwqw(475 zr)ZW%SZR~9kzgPPw00@rq~t>6y&>)PXtgCT6OR9_cubwcdG}yg8e$~btv*&azp?>; z#3w4^+V!@pg&b)3-yX7hq2${Q>tD$9C#Kh8&bo~}G)buBX*T~wiOp%@e?%AT5`7qL zx`!S~r`7zT=!5x`XSR4TQ6`|T=)%D%s}#3^{+`0L$>i?_tKGcvb5>`gL2N*v3Cy+>7@=?IxMnp7>c*CfqwQ7h*(#O0C((b zhH&5FHRuKX0wq-pe>$Acn>j7-T+=SSFUX|4tiUHQ6uM`1^2Xcfs^WE;o_EGpea^wL z?P=KNYZ*@CFdyH~h8WlSnCBslG8{7R*t~P^(9*hWnam8oN-K%uN>3K4EMSP23ecJ5 z>|)?ab0-{4fhY`j#SjsI2(|b@limk97tv=J!dA#^PmUDju1JZ?j{8^r|4D&YTIhSkk|=5Wk_`=iRZrR%OS4~;lovf# zgw+`7FP*A$l{i;*&v|NpZ?)T{i)ZwV*?<1W@<6L?z+K;TtUoAwhJc^u05Y-Hy`(iSd!D#TGAp zj0Bd^l}2-1izf!`4W&8>wu7qq3DxHR7Qxr99`0$dxkJk(DBwS!WXZT4^wOL>hGyei z_gq?oJ+m$PPpjgPk6`m9Ll@2iL~+nSY2&J8qY+@LT-Q&EVWa{d6&gKw|u zXp49D@}Lt9?AjGt@$(=xZ|XbKp--B?kT5JHJ*q)xGhCo7_-EEPC^D&XoAPJqX`6Dh zgBuj&PunKnl>gyBZU6TFwEh3rL0bQP9Z1+d`uz7#-%FeW9_V>f$2OFbaK>+atwc!A zc}O?GcB7&PT5c*%z!`%^)3XvoGJNmn<5nEx-FG=K(2VNB;Ew!YrH|~P(^HY2%er@C zFs{7%R^W-j{W`!@i9=B~CEBvCIBll9v_AV$O6xSE zLl$LxG-##ra&OC(A-NelhJT(66905D2bF~mxnDC&7zw5YNA?Ye_o_d~jMTBfY92v0 zbNT|ThC1CSz~+1f?ZuN?m1o`)e~}ua0^itb(Xo4DbpGn|5Mq;>d-gDgo3~m z8llYh`!7dpDiBla`|^R)J~%6P(-1Xf#nD>#zM^hgQ6sTrPw+W_W9%!boh^A@Ep|?$RtZCwds> z)OA2x*V~%}N)n29qL-9Zor}nITm!@yb|p2oZxGfQTD5K#>X*m!*6Z4;@U0)kInWS6 zS`)u#eOZ8(?14U|pNViAN1#2wirXT_dj^37YQ&s`>}Lz{i4=dMu1BD@6CR(rQta9T zWFPLs2EubrR2A1b8qO)K2Uxd2_Fj?Q8xnD@<=bsFJFseyE{E;)1HnUfz8ihJyrr2| z*Q>50)d;&gD+`-?(8#0w$$f-g-BV+N7si?S;+1VBMX}+Fj?J2-a#eB`hTOrL!}#h2 zjdNX`{TcpNiQ|Y@k;wTHfm2UGyCg*Q8IuS$oI~lg9xi*<^F&!qC#Ii3E0e6P*>`4; zgwwEW!Ld1yQ@X9;*zq)7MDXYOE^KpRM)gv6d3qa%`fg^vGeK7@vW{EG;S3Jpjqr~* zN|eT(cDz15!DbEEgL%!bzJROJX}1|Bp_-R5vqU(4`=yLp1L+v#y1?Sm&1HgFGz1Zj lhOovC|9$`f diff --git a/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png b/native/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png index cdd488d7b485b8543e2d91f855a673bc5479e466..a2c30be92b00ddb827aed9947182f0a6b6e9ae83 100644 GIT binary patch literal 1243 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 AjQ{`u literal 1995 zcmb`IdpHw}7sn|zyiBHxR*~0>QqHiOIrY zW_G)k_A4a!@8+<>)&emxKnC2*#Qwq962;2e9&<4Ey-f4@i;3cD-5MxQEk*sICO(U< zxx^i!S3w&6a_J3uxj7A$V|sp$wZ3+q4at27@AAR?TsQwZ4y(hWLPnbVH+Vlw9PnZz z1z(I0Kfl2qk6W=q$(|?T+M)AT=C?NtJ=8r^9UtX$FHe&OXB1W+?*UO{lCO-brMZ42 zeN3t;GUK=DPivjZM$fJ@J~(0)BU`Jobq~r~eYSnvAR7i-{IcTm$sF;9Vd(WTIIkPo+JXo>WxphECAymg~k?_ML!S99P{Y{G*HH7xaB&2i^VdK=ciUewb5G*Ifd zzT&?^zUx)LIPDD$N!>T?O@SPM?dO#h@_{s$Ib;U1d;FouxX%b>H<#CZoOr!dAO3JS zH7D)*N7Z$BTOSP3mEa=HM~wUoGx%M7M7`|WdA`#a*57g4_f_Gk_#j_g^xEXxwV6N} z3IU=WA6V4Pv{`R>p-!Xk%~fi8DS6B*`Niwa?-;qV6xxXN zsFp$$1$}J|h&ZoI64|eGE4qu%>sg%Gc{^$_(&3^&AzlK|yNRXG)$;$8q&ST6uuX(8 zknEk~V~xy^)Vp<)5ITxk`*aQCX%nyWjVb; zN3eUy3Sv-ksCb$}+AoovK~ab<(a~3GTziOkhJcwkJzpxEc(xIGtmWF^AKk)$HtO%uWi%3iU5al>OuwZK#w>kw4e#WMr4)L#U4opEu44$lDuro6=&;qu7M(620BW~*>ISK4aw z95|&HDZ*S%8fbaWmebwll0_qC{h6dFCCGrX6~oib$d5=3{kDX9L|Hfh2cfdenTV23 z&r#6tH^{fP+51;N`fQs&$(e2F353m+1TISVvo3L_U|fABYqT7Dp{YvaSnaLTa~|fr zlF+_C*u7+R=RSGeyC&7nTPJB3nU27&&v*Q3z^*(k-0M?P|Bd%Qlz-v=-`fAo{~xzi z)W((eLXq9bTXB%XyLMmy9^rnMtlO@)sVLBhbAJl7sMX%5hu}q*Y?3+oTOGSDt3|b~ z2nr>0<8#0?>uGDJ!peqW``(fK?QqxOzysgw&`Da(d(0j^HCO(=UZ56GUk;lF8Apif z%m(`0;P?eF;`A?4lhtRuzH%Q?7fbE6V-I;%>Rr~apZIY0ro{bpd4jHYwD<{XNDtV* zKGioWf2`d2Un&*jZHT0Ruj5Nsc@?XrDt)nhIYX;-Q;QRuNh~DKvN_l!+;x*6AUPzLfcwi6=*Yn>)CVTQ< z!kEISbe)h{O0pDgfEXB)qfIPPu(NgW{efxbMca3Fn%5#+59%}065%6R60sDqUa4NG zSjs=bO7TAW?UG8SOxe~b|yvBlT8Lca(PXP!LeCA)sTjp(HjQ}xaM?Ns9ma#AIrEQF7? z?BPT)D&mAdr!({XJSxtCD_0XU=b@@I@^q`T0Nx4+UpQ&MleVrg(#XDeV*}rx=T;xt ziJUXY8iI-DJSC;Glgn?vYh+t5N}5URGw!ml$(TwaQ-wCWH+iO?so{}76?`Ea^*0!4 z70yT@H&HB4fuH?0W)OD5L}iJ7aq$7x#}Ws6VqVwCP8&M>7hR zTdu03r^$93p3duoRBtRhIM*3qObz-1F4mS;Stlz;H*^N9E", baseURL: url) + webview.loadSimulatedRequest(URLRequest(url: url), responseHTML: "") var loaded = false for _ in 0..<50 { if let value = try? await webview.evaluateJavaScript("document.getElementById('draft')?.value"), @@ -57,10 +57,12 @@ final class WorkspaceTests: XCTestCase { XCTAssertTrue(webview.canGoBack) XCTAssertTrue(toolbar.items?.first?.isEnabled == true) webview.goBack() + var returned = false for _ in 0..<20 { - if let hash = try? await webview.evaluateJavaScript("location.hash"), hash as? String == "" { break } + if let hash = try? await webview.evaluateJavaScript("location.hash"), hash as? String == "" { returned = true; break } try await Task.sleep(for: .milliseconds(50)) } + XCTAssertTrue(returned) let draft = try await webview.evaluateJavaScript("document.getElementById('draft').value") XCTAssertEqual(draft as? String, "unsaved") } diff --git a/native/src-tauri/gen/apple/kanbangit-native.xcodeproj/project.pbxproj b/native/src-tauri/gen/apple/kanbangit-native.xcodeproj/project.pbxproj index ec3205a..7d6c1ee 100644 --- a/native/src-tauri/gen/apple/kanbangit-native.xcodeproj/project.pbxproj +++ b/native/src-tauri/gen/apple/kanbangit-native.xcodeproj/project.pbxproj @@ -49,7 +49,9 @@ 47C531812A31207F97A89C5F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 4C76059D31C50B198779A5F7 /* en.lproj */ = {isa = PBXFileReference; path = en.lproj; sourceTree = ""; }; 554AB059B4228085924F4959 /* WorkspacePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspacePolicy.swift; sourceTree = ""; }; + 5F71F06AB639140AB9714425 /* metadata.rs */ = {isa = PBXFileReference; path = metadata.rs; sourceTree = ""; }; 63685B1189795CAEA85F7AB7 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 6610D376CF901AC1733D394E /* updates.rs */ = {isa = PBXFileReference; path = updates.rs; sourceTree = ""; }; 7440C7F0426E6B3977DD1D94 /* main.rs */ = {isa = PBXFileReference; path = main.rs; sourceTree = ""; }; 8243D0C3821DAC731FECF105 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 91991DFDB0C064B06DF854ED /* libapp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapp.a; sourceTree = ""; }; @@ -146,7 +148,9 @@ 2B96972F58CC158AB3DFF94A /* lib.rs */, AEBAD9ABE758897C50BC1F2E /* locale.rs */, 7440C7F0426E6B3977DD1D94 /* main.rs */, + 5F71F06AB639140AB9714425 /* metadata.rs */, E8BA104C81071623E1FF367D /* server.rs */, + 6610D376CF901AC1733D394E /* updates.rs */, ); name = src; path = ../../src; @@ -269,12 +273,18 @@ LastUpgradeCheck = 1430; TargetAttributes = { 721CF221CEDDC28FF2C5EBD5 = { - DevelopmentTeam = ""; - ProvisioningStyle = Automatic; + DevelopmentTeam = ZZX2KZGTHU; + ProvisioningStyle = Manual; }; 988CFF304CB81C8F3A4D6612 = { + DevelopmentTeam = ZZX2KZGTHU; + ProvisioningStyle = Manual; TestTargetID = 721CF221CEDDC28FF2C5EBD5; }; + CBC4561F8018D37DD49A0598 = { + DevelopmentTeam = ZZX2KZGTHU; + ProvisioningStyle = Manual; + }; }; }; buildConfigurationList = A9CA551DD3F3EBCCF8C41CC1 /* Build configuration list for PBXProject "kanbangit-native" */; @@ -332,7 +342,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export PATH=\"$(dirname \"$(rustup which cargo)\"):$PATH\"\nexport RUSTC=\"$(rustup which rustc)\"\ncd \"$SRCROOT/../../..\"\n./node_modules/.bin/tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths \"${FRAMEWORK_SEARCH_PATHS:?}\" --header-search-paths \"${HEADER_SEARCH_PATHS:?}\" --gcc-preprocessor-definitions \"${GCC_PREPROCESSOR_DEFINITIONS:-}\" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}\n"; + shellScript = "set -eu\nif [ -n \"${KANBANGIT_IOS_LIBRARY:-}\" ]; then\n mkdir -p \"$SRCROOT/Externals/arm64/$CONFIGURATION\"\n cp \"$KANBANGIT_IOS_LIBRARY\" \"$SRCROOT/Externals/arm64/$CONFIGURATION/libapp.a\"\n exit 0\nfi\nexport PATH=\"$(dirname \"$(rustup which cargo)\"):$PATH\"\nexport RUSTC=\"$(rustup which rustc)\"\ncd \"$SRCROOT/../../..\"\n./node_modules/.bin/tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths \"${FRAMEWORK_SEARCH_PATHS:?}\" --header-search-paths \"${HEADER_SEARCH_PATHS:?}\" --gcc-preprocessor-definitions \"${GCC_PREPROCESSOR_DEFINITIONS:-}\" --configuration ${CONFIGURATION:?} ${FORCE_COLOR:-} ${ARCHS:?}\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -404,9 +414,10 @@ ); ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "kanbangit-native_iOS/kanbangit-native_iOS.entitlements"; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_IDENTITY = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ZZX2KZGTHU; ENABLE_BITCODE = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO; "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64; @@ -421,9 +432,10 @@ ); "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$(PLATFORM_NAME) $(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/$(PLATFORM_NAME)"; "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$(PLATFORM_NAME) $(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/$(PLATFORM_NAME)"; - PRODUCT_BUNDLE_IDENTIFIER = de.kanbangit.desktop; + MARKETING_VERSION = 0.2.1; + PRODUCT_BUNDLE_IDENTIFIER = de.kanbangit.ios; PRODUCT_MODULE_NAME = KanbanGitNative; - PRODUCT_NAME = "KanbanGit"; + PRODUCT_NAME = KanbanGit; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -498,13 +510,15 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ZZX2KZGTHU; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = de.kanbangit.uitests; + PRODUCT_BUNDLE_IDENTIFIER = de.kanbangit.ios.uitests; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -577,9 +591,10 @@ ); ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "kanbangit-native_iOS/kanbangit-native_iOS.entitlements"; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ZZX2KZGTHU; ENABLE_BITCODE = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO; "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64; @@ -594,9 +609,10 @@ ); "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$(PLATFORM_NAME) $(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/$(PLATFORM_NAME)"; "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$(PLATFORM_NAME) $(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/$(PLATFORM_NAME)"; - PRODUCT_BUNDLE_IDENTIFIER = de.kanbangit.desktop; + MARKETING_VERSION = 0.2.1; + PRODUCT_BUNDLE_IDENTIFIER = de.kanbangit.ios; PRODUCT_MODULE_NAME = KanbanGitNative; - PRODUCT_NAME = "KanbanGit"; + PRODUCT_NAME = KanbanGit; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -608,13 +624,15 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ZZX2KZGTHU; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = de.kanbangit.uitests; + PRODUCT_BUNDLE_IDENTIFIER = de.kanbangit.ios.uitests; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -626,13 +644,15 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ZZX2KZGTHU; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = de.kanbangit.tests; + PRODUCT_BUNDLE_IDENTIFIER = de.kanbangit.ios.tests; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -643,13 +663,15 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ZZX2KZGTHU; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = de.kanbangit.tests; + PRODUCT_BUNDLE_IDENTIFIER = de.kanbangit.ios.tests; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/native/src-tauri/gen/apple/kanbangit-native_iOS/Info.plist b/native/src-tauri/gen/apple/kanbangit-native_iOS/Info.plist index d20aab9..124cc20 100644 --- a/native/src-tauri/gen/apple/kanbangit-native_iOS/Info.plist +++ b/native/src-tauri/gen/apple/kanbangit-native_iOS/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.1.0 + $(MARKETING_VERSION) CFBundleVersion - 0.1.0 + $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS NSCameraUsageDescription @@ -45,4 +45,4 @@ UIInterfaceOrientationLandscapeRight - \ No newline at end of file + diff --git a/native/src-tauri/gen/apple/project.yml b/native/src-tauri/gen/apple/project.yml index d2d3e82..4eadb84 100644 --- a/native/src-tauri/gen/apple/project.yml +++ b/native/src-tauri/gen/apple/project.yml @@ -1,6 +1,6 @@ name: kanbangit-native options: - bundleIdPrefix: de.kanbangit.desktop + bundleIdPrefix: de.kanbangit.ios deploymentTarget: iOS: 17.0 fileGroups: [../../src] @@ -14,10 +14,17 @@ settingGroups: SWIFT_VERSION: "5.0" PRODUCT_MODULE_NAME: KanbanGitNative TARGETED_DEVICE_FAMILY: "1,2" - CODE_SIGN_STYLE: Automatic - DEVELOPMENT_TEAM: "" + CODE_SIGN_STYLE: Manual + DEVELOPMENT_TEAM: ZZX2KZGTHU ENABLE_USER_SCRIPT_SANDBOXING: false - PRODUCT_BUNDLE_IDENTIFIER: de.kanbangit.desktop + PRODUCT_BUNDLE_IDENTIFIER: de.kanbangit.ios + MARKETING_VERSION: "0.2.1" + CURRENT_PROJECT_VERSION: "1" + configs: + debug: + CODE_SIGN_IDENTITY: Apple Development + release: + CODE_SIGN_IDENTITY: Apple Distribution targetTemplates: app: type: application @@ -55,8 +62,8 @@ targets: - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - CFBundleShortVersionString: 0.1.0 - CFBundleVersion: "0.1.0" + CFBundleShortVersionString: $(MARKETING_VERSION) + CFBundleVersion: $(CURRENT_PROJECT_VERSION) entitlements: path: kanbangit-native_iOS/kanbangit-native_iOS.entitlements scheme: @@ -88,10 +95,16 @@ targets: - sdk: WebKit.framework preBuildScripts: - script: | + set -eu + if [ -n "${KANBANGIT_IOS_LIBRARY:-}" ]; then + mkdir -p "$SRCROOT/Externals/arm64/$CONFIGURATION" + cp "$KANBANGIT_IOS_LIBRARY" "$SRCROOT/Externals/arm64/$CONFIGURATION/libapp.a" + exit 0 + fi export PATH="$(dirname "$(rustup which cargo)"):$PATH" export RUSTC="$(rustup which rustc)" cd "$SRCROOT/../../.." - ./node_modules/.bin/tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths "${FRAMEWORK_SEARCH_PATHS:?}" --header-search-paths "${HEADER_SEARCH_PATHS:?}" --gcc-preprocessor-definitions "${GCC_PREPROCESSOR_DEFINITIONS:-}" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?} + ./node_modules/.bin/tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths "${FRAMEWORK_SEARCH_PATHS:?}" --header-search-paths "${HEADER_SEARCH_PATHS:?}" --gcc-preprocessor-definitions "${GCC_PREPROCESSOR_DEFINITIONS:-}" --configuration ${CONFIGURATION:?} ${FORCE_COLOR:-} ${ARCHS:?} name: Build Rust Code basedOnDependencyAnalysis: false outputFiles: @@ -108,7 +121,9 @@ targets: settings: base: GENERATE_INFOPLIST_FILE: YES - PRODUCT_BUNDLE_IDENTIFIER: de.kanbangit.tests + PRODUCT_BUNDLE_IDENTIFIER: de.kanbangit.ios.tests + DEVELOPMENT_TEAM: ZZX2KZGTHU + CODE_SIGN_STYLE: Manual SWIFT_VERSION: "5.0" WorkspaceUITests: type: bundle.ui-testing @@ -119,6 +134,8 @@ targets: settings: base: GENERATE_INFOPLIST_FILE: YES - PRODUCT_BUNDLE_IDENTIFIER: de.kanbangit.uitests + PRODUCT_BUNDLE_IDENTIFIER: de.kanbangit.ios.uitests + DEVELOPMENT_TEAM: ZZX2KZGTHU + CODE_SIGN_STYLE: Manual SWIFT_VERSION: "5.0" TEST_TARGET_NAME: kanbangit-native_iOS diff --git a/native/src-tauri/tauri.ios.conf.json b/native/src-tauri/tauri.ios.conf.json index 6722463..9721cb6 100644 --- a/native/src-tauri/tauri.ios.conf.json +++ b/native/src-tauri/tauri.ios.conf.json @@ -1,6 +1,11 @@ { "$schema": "https://schema.tauri.app/config/2", + "identifier": "de.kanbangit.ios", "bundle": { - "iOS": { "minimumSystemVersion": "17.0" } + "iOS": { + "minimumSystemVersion": "17.0", + "developmentTeam": "ZZX2KZGTHU", + "bundleVersion": "1" + } } } diff --git a/native/tests/ios-build.test.js b/native/tests/ios-build.test.js new file mode 100644 index 0000000..7462bd5 --- /dev/null +++ b/native/tests/ios-build.test.js @@ -0,0 +1,534 @@ +import assert from 'node:assert/strict'; +import { Buffer } from 'node:buffer'; +import { createHash } from 'node:crypto'; +import { + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { + appleTeamId, + buildIos, + bundleIdentifier, + selectIdentity, + validateProfile, +} from '../build-ios.mjs'; + +const certificate = Buffer.from('synthetic test certificate'); +const fingerprint = createHash('sha1') + .update(certificate) + .digest('hex') + .toUpperCase(); + +function profileFor(mode = 'testflight') { + return { + UUID: '00000000-1111-2222-3333-444444444444', + TeamIdentifier: [appleTeamId], + Platform: ['iOS'], + ExpirationDate: '2099-01-01T00:00:00Z', + DeveloperCertificates: [certificate.toString('base64')], + ...(mode === 'development' + ? { ProvisionedDevices: ['test-device'] } + : {}), + Entitlements: { + 'application-identifier': `${appleTeamId}.${bundleIdentifier}`, + 'com.apple.developer.team-identifier': appleTeamId, + 'get-task-allow': mode === 'development', + 'beta-reports-active': mode === 'testflight', + }, + }; +} + +function fixture(t, mode = 'testflight', overrides = {}) { + const root = mkdtempSync(join(tmpdir(), 'kanbangit-ios-test-')); + t.after(() => rmSync(root, { recursive: true, force: true })); + const directory = join(root, 'repo/native'); + const profilePath = join(root, 'profile.mobileprovision'); + const config = { + productName: 'KanbanGit', + identifier: 'de.kanbangit.desktop', + version: '1.2.3', + }; + const ios = { + identifier: bundleIdentifier, + bundle: { + iOS: { + developmentTeam: appleTeamId, + bundleVersion: '7', + minimumSystemVersion: '17.0', + }, + }, + }; + mkdirSync(join(directory, 'src-tauri'), { recursive: true }); + writeFileSync( + join(directory, 'src-tauri/tauri.conf.json'), + JSON.stringify(config), + ); + writeFileSync( + join(directory, 'src-tauri/tauri.ios.conf.json'), + JSON.stringify(ios), + ); + writeFileSync(profilePath, 'external provisioning profile'); + const profile = profileFor(mode); + const calls = []; + const destination = join(directory, `src-tauri/target/ios-${mode}/1.2.3-7`); + const home = join(root, 'home'); + const installedProfile = join( + home, + `Library/Developer/Xcode/UserData/Provisioning Profiles/${profile.UUID}.mobileprovision`, + ); + const info = { + CFBundleIdentifier: bundleIdentifier, + CFBundleShortVersionString: '1.2.3', + CFBundleVersion: '7', + }; + function app(path) { + mkdirSync(path, { recursive: true }); + writeFileSync( + join(path, 'Info.plist'), + JSON.stringify({ ...info, ...overrides.info }), + ); + } + const options = { + directory, + home, + mode, + platform: 'darwin', + log() {}, + env: { PATH: '/test/bin', IOS_PROVISIONING_PROFILE: profilePath }, + run(command, args, settings) { + calls.push({ command, args, settings }); + if (overrides.failStep === settings.step) { + throw new Error('Simulated build failure'); + } + if (command === 'python3') { + return settings.input.toString(); + } + if (command === 'security') { + if (args[0] === 'cms') { + const embedded = args + .at(-1) + .endsWith('embedded.mobileprovision'); + return JSON.stringify( + embedded && overrides.embeddedProfile + ? overrides.embeddedProfile + : profile, + ); + } + return `1) ${fingerprint} "Apple ${mode === 'development' ? 'Development' : 'Distribution'}: Example (${appleTeamId})"`; + } + if (command === 'rustup') { + if (args[0] === 'target') { + return ( + overrides.targets ?? + 'aarch64-apple-ios\naarch64-apple-ios-sim' + ); + } + return `/permanent/toolchain/bin/${args[1]}`; + } + if (command === '/permanent/toolchain/bin/cargo') { + const library = join( + directory, + 'src-tauri/target', + args[args.indexOf('--target') + 1], + args.includes('--release') ? 'release' : 'debug', + 'libkanbangit_native.a', + ); + mkdirSync(dirname(library), { recursive: true }); + writeFileSync(library, 'compiled library'); + } + if (command === 'xcodebuild' && args.includes('archive')) { + assert.ok(mode === 'simulator' || existsSync(installedProfile)); + app( + join( + args[args.indexOf('-archivePath') + 1], + 'Products/Applications/KanbanGit.app', + ), + ); + } + if (command === 'xcodebuild' && args.includes('-exportArchive')) { + const output = args[args.indexOf('-exportPath') + 1]; + mkdirSync(output, { recursive: true }); + if (!overrides.missingIpa) { + writeFileSync( + join(output, 'KanbanGit.ipa'), + 'signed ipa bytes', + ); + } + } + if (command === 'ditto') { + app(join(args.at(-1), 'Payload/KanbanGit.app')); + } + if (command === 'xcrun') { + return '/permanent/Xcode/SDK'; + } + return ''; + }, + }; + return { + options, + calls, + profile, + profilePath, + destination, + installedProfile, + ios, + }; +} + +test('TestFlight produces a verified local IPA with manual signing and no Apple mutation or upload flags', (t) => { + const f = fixture(t); + f.options.env.APPLE_API_KEY = 'must-not-reach-build'; + f.options.env.IOS_CERTIFICATE = 'must-not-reach-build'; + assert.equal(buildIos(f.options), f.destination); + const options = JSON.parse( + readFileSync(join(f.destination, 'ExportOptions.plist'), 'utf8'), + ); + assert.equal(options.method, 'app-store-connect'); + assert.equal(options.destination, 'export'); + assert.equal(options.signingStyle, 'manual'); + assert.equal(options.manageAppVersionAndBuildNumber, false); + assert.equal(options.signingCertificate, fingerprint); + assert.deepEqual(options.provisioningProfiles, { + [bundleIdentifier]: f.profile.UUID, + }); + assert.equal( + readFileSync( + join(f.destination, 'export/KanbanGit.ipa.sha256'), + 'utf8', + ), + `${createHash('sha256').update('signed ipa bytes').digest('hex')} KanbanGit.ipa\n`, + ); + assert.equal( + f.calls.filter( + (call) => + call.command === 'codesign' && call.args.includes('--verify'), + ).length, + 2, + ); + assert.equal(existsSync(f.installedProfile), false); + const verification = f.calls.find((call) => call.command === 'codesign'); + const requirement = verification.args[verification.args.indexOf('-R') + 1]; + assert.ok(requirement.includes('! entitlement["get-task-allow"] exists')); + assert.ok( + requirement.includes('entitlement["beta-reports-active"] exists'), + ); + assert.ok(requirement.includes(`certificate leaf = H"${fingerprint}"`)); + for (const call of f.calls) { + assert.equal(call.settings.env.APPLE_API_KEY, undefined); + assert.equal(call.settings.env.IOS_CERTIFICATE, undefined); + assert.ok( + !call.args.some((arg) => + /allowProvisioning|authenticationKey|upload|downloadPlatform/.test( + arg, + ), + ), + ); + } + const archive = f.calls.find((call) => call.args.includes('archive')); + assert.ok(archive.args.includes('CODE_SIGN_STYLE=Manual')); + assert.ok( + archive.args.includes( + `PROVISIONING_PROFILE_SPECIFIER=${f.profile.UUID}`, + ), + ); + assert.ok( + archive.settings.env.KANBANGIT_IOS_LIBRARY.endsWith( + '/aarch64-apple-ios/release/libkanbangit_native.a', + ), + ); +}); + +test('the simulator needs no certificate or profile and disables signing explicitly', (t) => { + const f = fixture(t, 'simulator'); + delete f.options.env.IOS_PROVISIONING_PROFILE; + buildIos(f.options); + assert.ok( + f.calls.some((call) => call.args.includes('CODE_SIGNING_ALLOWED=NO')), + ); + assert.ok( + f.calls.some((call) => call.args.includes('aarch64-apple-ios-sim')), + ); + assert.ok( + !f.calls.some( + (call) => + ['security', 'codesign'].includes(call.command) || + call.args.includes('-exportArchive'), + ), + ); +}); + +test('local device development uses a registered-device development profile', (t) => { + const f = fixture(t, 'development'); + buildIos(f.options); + const options = JSON.parse( + readFileSync(join(f.destination, 'ExportOptions.plist'), 'utf8'), + ); + assert.equal(options.method, 'debugging'); + assert.equal(options.destination, 'export'); +}); + +for (const [name, change, message] of [ + [ + 'different team', + (p) => { + p.TeamIdentifier = ['OLDTEAM']; + }, + /explicitly match/, + ], + [ + 'wildcard identifier', + (p) => { + p.Entitlements['application-identifier'] = `${appleTeamId}.*`; + }, + /explicitly match/, + ], + [ + 'macOS identifier', + (p) => { + p.Entitlements['application-identifier'] = + `${appleTeamId}.de.kanbangit.desktop`; + }, + /explicitly match/, + ], + [ + 'expired profile', + (p) => { + p.ExpirationDate = '2000-01-01'; + }, + /expired/, + ], + [ + 'invalid expiry', + (p) => { + p.ExpirationDate = 'invalid'; + }, + /expired/, + ], + [ + 'invalid UUID', + (p) => { + p.UUID = '../unexpected'; + }, + /invalid/, + ], + [ + 'missing certificate', + (p) => { + p.DeveloperCertificates = []; + }, + /invalid/, + ], + [ + 'wrong platform', + (p) => { + p.Platform = ['OSX']; + }, + /iOS profile/, + ], + [ + 'Ad Hoc profile', + (p) => { + p.ProvisionedDevices = ['device']; + }, + /App Store Connect/, + ], + [ + 'enterprise profile', + (p) => { + p.ProvisionsAllDevices = true; + }, + /App Store Connect/, + ], + [ + 'development entitlement', + (p) => { + p.Entitlements['get-task-allow'] = true; + }, + /App Store Connect/, + ], + [ + 'missing beta entitlement', + (p) => { + delete p.Entitlements['beta-reports-active']; + }, + /App Store Connect/, + ], +]) { + test(`rejects ${name} before signing`, () => { + const profile = profileFor(); + change(profile); + assert.throws(() => validateProfile(profile, 'testflight'), message); + }); +} + +test('a distribution profile cannot be used for device development', () => { + assert.throws( + () => validateProfile(profileFor(), 'development'), + /App Development/, + ); +}); + +test('identity selection requires the profile certificate and a usable private key of the correct kind', () => { + const profile = profileFor(); + for (const identities of [ + '0 valid identities found', + `1) ${'A'.repeat(40)} "Apple Distribution: Other"`, + `1) ${fingerprint} "Developer ID Application: Example"`, + `1) ${fingerprint} "Apple Development: Example"`, + ]) { + assert.throws( + () => selectIdentity(profile, identities, 'testflight'), + /private key/, + ); + } +}); + +for (const step of [ + 'Generating the Xcode project', + 'Building the iOS Rust library', + 'Archiving locally without Apple provisioning updates', + 'Verifying the Apple signature and team', + 'Exporting the signed IPA locally (no upload)', +]) { + test(`failure at ${step} does not publish a successful build and cleans its profile`, (t) => { + const f = fixture(t, 'testflight', { failStep: step }); + assert.throws(() => buildIos(f.options), /Simulated build failure/); + assert.equal(existsSync(f.destination), false); + assert.equal(existsSync(f.installedProfile), false); + }); +} + +test('an existing identical local profile is preserved after the build', (t) => { + const f = fixture(t); + mkdirSync(dirname(f.installedProfile), { recursive: true }); + writeFileSync(f.installedProfile, readFileSync(f.profilePath)); + buildIos(f.options); + assert.equal(existsSync(f.installedProfile), true); +}); + +test('a conflicting local profile is never overwritten or deleted', (t) => { + const f = fixture(t); + mkdirSync(dirname(f.installedProfile), { recursive: true }); + writeFileSync(f.installedProfile, 'user profile'); + assert.throws(() => buildIos(f.options), /same UUID/); + assert.equal(readFileSync(f.installedProfile, 'utf8'), 'user profile'); +}); + +test('missing toolchain targets fail before compilation without installing anything', (t) => { + const f = fixture(t, 'testflight', { targets: 'aarch64-apple-darwin' }); + assert.throws( + () => buildIos(f.options), + /rustup target add aarch64-apple-ios/, + ); + assert.ok( + !f.calls.some( + (call) => call.args.includes('build') || call.args.includes('add'), + ), + ); +}); + +test('missing profile and ambient team overrides fail clearly', (t) => { + const f = fixture(t); + delete f.options.env.IOS_PROVISIONING_PROFILE; + assert.throws(() => buildIos(f.options), /IOS_PROVISIONING_PROFILE/); + f.options.env.APPLE_DEVELOPMENT_TEAM = 'OLDTEAM'; + assert.throws(() => buildIos(f.options), /must be ZZX2KZGTHU/); + delete f.options.env.APPLE_DEVELOPMENT_TEAM; + f.options.env.TAURI_CONFIG = '{}'; + assert.throws(() => buildIos(f.options), /Remove TAURI_CONFIG/); +}); + +for (const [name, overrides, message] of [ + [ + 'wrong app version', + { info: { CFBundleVersion: '8' } }, + /unexpected identifier, version/, + ], + [ + 'changed embedded profile', + { + embeddedProfile: { + ...profileFor(), + UUID: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', + }, + }, + /unexpected provisioning profile/, + ], + ['missing IPA', { missingIpa: true }, /did not produce/], +]) { + test(`rejects ${name} in the build output`, (t) => { + const f = fixture(t, 'testflight', overrides); + assert.throws(() => buildIos(f.options), message); + assert.equal(existsSync(f.destination), false); + }); +} + +test('completed artifacts are not silently overwritten', (t) => { + const f = fixture(t); + buildIos(f.options); + assert.throws(() => buildIos(f.options), /already exists/); +}); + +for (const mode of ['simulator', 'development']) { + test(`${mode} can rebuild the same version and preserves the previous build on failure`, (t) => { + const overrides = {}; + const f = fixture(t, mode, overrides); + buildIos(f.options); + const previousBuild = join(f.destination, 'previous-build'); + writeFileSync(previousBuild, 'verified artifact'); + overrides.failStep = 'Building the iOS Rust library'; + assert.throws(() => buildIos(f.options), /Simulated build failure/); + assert.equal(readFileSync(previousBuild, 'utf8'), 'verified artifact'); + delete overrides.failStep; + buildIos(f.options); + assert.equal(existsSync(previousBuild), false); + assert.ok(existsSync(join(f.destination, 'KanbanGit.xcarchive'))); + assert.equal(existsSync(f.installedProfile), false); + }); +} + +test('every iOS icon has its declared dimensions and contains no alpha channel or transparency chunk', () => { + const assets = fileURLToPath( + new URL( + '../src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/', + import.meta.url, + ), + ); + const catalog = JSON.parse( + readFileSync(join(assets, 'Contents.json'), 'utf8'), + ); + assert.ok( + catalog.images.some( + (icon) => + icon.idiom === 'ios-marketing' && icon.size === '1024x1024', + ), + ); + for (const icon of catalog.images) { + const png = readFileSync(join(assets, icon.filename)); + assert.equal(png.subarray(0, 8).toString('hex'), '89504e470d0a1a0a'); + const [width, height] = icon.size.split('x').map(Number); + const scale = Number.parseInt(icon.scale, 10); + assert.equal(png.readUInt32BE(16), width * scale, icon.filename); + assert.equal(png.readUInt32BE(20), height * scale, icon.filename); + assert.equal(png[25], 2, `${icon.filename} must use RGB without alpha`); + for ( + let offset = 8; + offset < png.length; + offset += png.readUInt32BE(offset) + 12 + ) { + assert.notEqual( + png.subarray(offset + 4, offset + 8).toString(), + 'tRNS', + icon.filename, + ); + } + } +}); -- 2.54.0