diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 233133d..3716f0b 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -171,12 +171,13 @@ jobs: Copy-Item -Path "dist\OpenReader\*" -Destination $stageDir -Recurse -Force # Copy and patch manifest - # PowerShell -replace is case-insensitive; use -creplace to avoid - # matching version="1.0" in the XML declaration. + # Use XML DOM patching (not regex) to avoid corrupting MinVersion/MaxVersionTested Copy-Item -Path "packaging\msix\AppxManifest.xml" -Destination "$stageDir\AppxManifest.xml" -Force - $manifestContent = [System.IO.File]::ReadAllText("$stageDir\AppxManifest.xml") - $manifestContent = $manifestContent -creplace 'Version="[^"]+"', "Version=`"$msixVersion`"" - [System.IO.File]::WriteAllText("$stageDir\AppxManifest.xml", $manifestContent, [System.Text.UTF8Encoding]$false) + $manifestXml = New-Object System.Xml.XmlDocument + $manifestXml.PreserveWhitespace = $true + $manifestXml.Load("$stageDir\AppxManifest.xml") + $manifestXml.Package.Identity.Version = $msixVersion + $manifestXml.Save("$stageDir\AppxManifest.xml") # Generate MSIX asset placeholders using Python Write-Host "=== Generating MSIX asset placeholders ===" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7e49f0b..36e1483 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -131,15 +131,15 @@ jobs: Copy-Item -Path "dist\OpenReader\*" -Destination $stageDir -Recurse -Force # Copy and patch manifest - # CRITICAL: PowerShell -replace is CASE-INSENSITIVE by default. - # Regex 'Version="[^"]+"' matches version="1.0" in XML declaration too! - # Use -creplace (case-sensitive) to only match Identity element's Version. + # Use XML DOM patching (not regex) to avoid corrupting MinVersion/MaxVersionTested Copy-Item -Path "packaging\msix\AppxManifest.xml" -Destination "$stageDir\AppxManifest.xml" -Force $manifestPath = "$stageDir\AppxManifest.xml" - $content = [System.IO.File]::ReadAllText($manifestPath) - $content = $content -creplace 'Version="[^"]+"', "Version=`"$msixVersion`"" - [System.IO.File]::WriteAllText($manifestPath, $content, [System.Text.UTF8Encoding]$false) - Write-Host "Manifest patched: Version -> $msixVersion" + $manifestXml = New-Object System.Xml.XmlDocument + $manifestXml.PreserveWhitespace = $true + $manifestXml.Load($manifestPath) + $manifestXml.Package.Identity.Version = $msixVersion + $manifestXml.Save($manifestPath) + Write-Host "Manifest patched: Identity Version -> $msixVersion" Write-Host "" # Validate XML before MakeAppx @@ -147,9 +147,11 @@ jobs: try { $xml = [xml](Get-Content $manifestPath -Raw) Write-Host "XML parsed successfully." - Write-Host "Identity: $($xml.Package.Identity.Name)" - Write-Host "Version: $($xml.Package.Identity.Version)" - Write-Host "Publisher: $($xml.Package.Identity.Publisher)" + Write-Host "Identity: $($xml.Package.Identity.Name)" + Write-Host "Version: $($xml.Package.Identity.Version)" + Write-Host "Publisher: $($xml.Package.Identity.Publisher)" + Write-Host "MinVersion: $($xml.Package.TargetDeviceFamily.MinVersion)" + Write-Host "MaxVersion: $($xml.Package.TargetDeviceFamily.MaxVersionTested)" } catch { Write-Host "XML PARSE ERROR: $_" Write-Host "First 20 lines of manifest:" @@ -213,9 +215,15 @@ jobs: Write-Host "=== MSIX built: $msixName (unsigned) ===" # Generate AppInstaller - $installerContent = [System.IO.File]::ReadAllText("packaging\msix\AppInstaller.xml") - $installerContent = $installerContent -creplace 'Version="[^"]+"', "Version=`"$msixVersion`"" - [System.IO.File]::WriteAllText("$pwd\OpenReader.appinstaller", $installerContent, [System.Text.UTF8Encoding]$false) + # Use XML DOM patching to only update MainPackage version (not AppInstaller schema version) + $installerXml = New-Object System.Xml.XmlDocument + $installerXml.PreserveWhitespace = $true + $installerXml.Load("packaging\msix\AppInstaller.xml") + $nsm = New-Object System.Xml.XmlNamespaceManager($installerXml.NameTable) + $nsm.AddNamespace("ai", "http://schemas.microsoft.com/appx/appinstaller/2021") + $mainPkg = $installerXml.SelectSingleNode("//ai:MainPackage", $nsm) + if ($mainPkg) { $mainPkg.Version = $msixVersion } + $installerXml.Save("$pwd\OpenReader.appinstaller") # Clean up Remove-Item -Recurse -Force $stageDir diff --git a/CHANGELOG.md b/CHANGELOG.md index 5266f7f..1894424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v1.2.2 — Store Submission Fix — 2026-06-18 + +- **Version:** Bumped `__version__` to `1.2.2`, MSIX version to `1.2.2.0`. +- **Fixed:** Partner Center package validation rejection (`MinVersion <= 10.0.17134.0`). +- **Root cause:** MSIX manifest patching used regex `-creplace 'Version="[^"]+"'` which matched inside `MinVersion` and `MaxVersionTested` attributes, overwriting them with the app version number. +- **Fix:** Replaced regex-based version injection with proper XML DOM patching in: + - `release.yml` (CI release build) + - `build-windows.yml` (CI dev build) + - `build-msix.ps1` (local build script) +- **Result:** Generated MSIX now correctly preserves `TargetDeviceFamily MinVersion="10.0.17763.0"`. +- **Validation added:** CI now logs `MinVersion` and `MaxVersionTested` during XML validation step. + ## v1.2.1 — First Public Microsoft Store Release Candidate — 2026-06-18 - **Version:** Bumped `__version__` to `1.2.1`. diff --git a/README.md b/README.md index ed0055e..322a40d 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ OpenReader is a **stable, local-first desktop PDF utility** built with Python, P The app is intentionally local-first: PDFs are opened, rendered, searched, merged, split, annotated, and compressed on your computer — no uploads, no accounts, no telemetry. -**v1.2.1** (current release) is the first public Microsoft Store release candidate. Windows distribution uses MSIX/App Installer with Windows-native updates — the app never replaces itself. See the [changelog](CHANGELOG.md) and [roadmap](ROADMAP.md) for what's new and what's next. +**v1.2.2** (current release) fixes the MSIX manifest for Microsoft Store acceptance. Windows distribution uses MSIX/App Installer with Windows-native updates — the app never replaces itself. See the [changelog](CHANGELOG.md) and [roadmap](ROADMAP.md) for what's new and what's next. ## Download diff --git a/RELEASE.md b/RELEASE.md index c46371c..21a5b6f 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -5,8 +5,8 @@ OpenReader uses semantic version tags to publish packaged builds. ## Version Source of Truth - `__version__` in `main.py` is the canonical source. Set it to the next release version. -- Tags must use the format `vMAJOR.MINOR.PATCH`, for example `v1.2.1`. -- The injected runtime version removes the leading `v`, so `v1.2.1` becomes `__version__ = "1.2.1"` in packaged builds. +- Tags must use the format `vMAJOR.MINOR.PATCH`, for example `v1.2.2`. +- The injected runtime version removes the leading `v`, so `v1.2.2` becomes `__version__ = "1.2.1"` in packaged builds. - CI injects the tag version for release builds via `scripts/inject_version.py`. ## Release Architecture (v1.2.0+) @@ -45,8 +45,8 @@ no separate code-signing certificate is needed. 4. Create and push a semantic version tag: ```bash - git tag v1.2.1 - git push origin v1.2.1 + git tag v1.2.2 + git push origin v1.2.2 ``` 5. GitHub Actions runs `.github/workflows/release.yml`. @@ -98,7 +98,7 @@ curl https://api.github.com/repos/sparshsam/pdfreader-by-sparsh/releases/latest The MSIX package is currently unsigned. The distribution plan is: 1. **Microsoft Store** — Submit the unsigned MSIX to the Microsoft Store. The Store - signs the package automatically with its Store identity. **v1.2.1 is the first + signs the package automatically with its Store identity. **v1.2.2 is the first Store release candidate.** 2. **Sideloading** — Unsigned MSIX from GitHub Releases requires Windows Developer Mode. Local test-signing scripts are in `packaging/msix/`. diff --git a/VERSIONING.md b/VERSIONING.md index 38904cb..bf50757 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -38,6 +38,7 @@ MSIX packages use a 4-part version: `major.minor.patch.build`. | `v1.2.0-rc.1` | `1.2.0.2` | | `v1.2.0` | `1.2.0.0` | | `v1.2.1` | `1.2.1.0` | +| `v1.2.2` | `1.2.2.0` | The build number is the prerelease index minus 1 (beta.1 → 0, beta.2 → 1, etc.). Stable releases use build 0. The CI workflow automatically extracts the correct diff --git a/docs/store-submission-checklist.md b/docs/store-submission-checklist.md index 02bd37f..c9db48e 100644 --- a/docs/store-submission-checklist.md +++ b/docs/store-submission-checklist.md @@ -1,11 +1,11 @@ # Microsoft Store Submission Checklist — OpenReader -**Target version:** v1.2.1 stable (MSIX version `1.2.1.0`) +**Target version:** v1.2.2 stable (MSIX version `1.2.2.0`) **Store ID:** `9MXDVW2645LL` **PFN:** `SparshSam.OpenReader_yh0byntbzd2qw` **Status:** 🔜 Ready for submission (privacy policy published) **Privacy policy URL:** https://sparshsam.github.io/pdfreader-by-sparsh/privacy/ -**Upload artifact:** `OpenReader.msix` from v1.2.1 GitHub Release (built by release.yml workflow) +**Upload artifact:** `OpenReader.msix` from v1.2.2 GitHub Release (built by release.yml workflow) --- @@ -24,7 +24,7 @@ Get-AppxPackage SparshSam.OpenReader | Select Name, Version, PackageFamilyName ```text Name Version PackageFamilyName ---- ------- ----------------- -SparshSam.OpenReader 1.2.1.0 SparshSam.OpenReader_yh0byntbzd2qw +SparshSam.OpenReader 1.2.2.0 SparshSam.OpenReader_yh0byntbzd2qw ``` ### 1.2 Manifest Audit @@ -44,10 +44,11 @@ Select-Xml -Path .\msix-check\AppxManifest.xml -XPath "//*[local-name()='Identit - [ ] `` - [ ] `` -- [ ] Version is `1.2.1.0` +- [ ] Version is `1.2.2.0` - [ ] `OpenReader` - [ ] `Sparsh Sam` - [ ] Executable is `OpenReader.exe` +- [ ] `TargetDeviceFamily MinVersion="10.0.17763.0"` (must be > 10.0.17134.0 for Store) - [ ] No placeholder or draft values remain ### 1.3 Capability Review @@ -111,7 +112,7 @@ Start-Process "OpenReader" 1. Navigate to **Partner Center** → OpenReader → **Packages** 2. Upload the **unsigned** `OpenReader.msix` from the GitHub Release 3. The Store will automatically sign the package with its Store identity -4. Set `1.2.1.0` as the version in Partner Center (must match manifest) +4. Set `1.2.2.0` as the version in Partner Center (must match manifest) 5. Submit for certification > **ℹ️** Upload the MSIX produced by the GitHub Actions release workflow directly. @@ -150,7 +151,7 @@ Start-Process "OpenReader" | **`runFullTrust` capability** | Store may ask why a desktop app needs full trust | Expected for Win32 desktop bridge apps. Document in submission notes: *"Desktop PDF reader using PySide6 — requires full trust for file system access and window management."* | | **App description claims** | Store may reject if claims are unrealistic | Keep description factual and shipping-feature-only. Remove roadmap items from Store description. | | **Unsplash/mock screenshots** | Store requires real app screenshots | Use actual app screenshots from `assets/` | -| **Version mismatch** | Upload rejected if manifest version ≠ Partner Center version | Verify `1.2.1.0` matches everywhere | +| **Version mismatch** | Upload rejected if manifest version ≠ Partner Center version | Verify `1.2.2.0` matches everywhere | | **Store ID reuse** | Cannot reuse Store ID for a different app | Reserved ID `9MXDVW2645LL` is tied to OpenReader — do not reassign | ### 4.2 Certification Notes for Submission @@ -203,7 +204,7 @@ Get-AppxPackage SparshSam.OpenReader | Select Name, Version, PackageFamilyName ```text Name Version PackageFamilyName ---- ------- ----------------- -SparshSam.OpenReader 1.2.1.0 SparshSam.OpenReader_yh0byntbzd2qw +SparshSam.OpenReader 1.2.2.0 SparshSam.OpenReader_yh0byntbzd2qw ``` ### 5.3 Functional Smoke Test diff --git a/main.py b/main.py index a1e4d5e..4cb83c4 100644 --- a/main.py +++ b/main.py @@ -50,7 +50,7 @@ ) -__version__ = "1.2.1" +__version__ = "1.2.2" GITHUB_REPO = "sparshsam/pdfreader-by-sparsh" IPC_SERVER_NAME = "OpenReader-IPC" RECENT_FILES_MAX = 10 diff --git a/packaging/msix/AppInstaller.xml b/packaging/msix/AppInstaller.xml index bd9ae0c..b384b9d 100644 --- a/packaging/msix/AppInstaller.xml +++ b/packaging/msix/AppInstaller.xml @@ -18,7 +18,7 @@ diff --git a/packaging/msix/AppxManifest.xml b/packaging/msix/AppxManifest.xml index 8196853..11c0438 100644 --- a/packaging/msix/AppxManifest.xml +++ b/packaging/msix/AppxManifest.xml @@ -24,7 +24,7 @@ + Version="1.2.2.0" /> OpenReader diff --git a/packaging/msix/README.md b/packaging/msix/README.md index e429525..e4b72af 100644 --- a/packaging/msix/README.md +++ b/packaging/msix/README.md @@ -136,6 +136,7 @@ MSIX follows a `major.minor.patch.build` version scheme. |---------|--------------|-------------| | `v1.2.0` | `1.2.0.0` | Initial MSIX release | | `v1.2.1` | `1.2.1.0` | First Microsoft Store release candidate | +| `v1.2.2` | `1.2.2.0` | Store submission fix (MinVersion patch bug) | The CI workflow automatically extracts the version from the Git tag and injects it into the manifest as `{tag}.0` (padded to 4 parts). diff --git a/packaging/msix/build-msix.ps1 b/packaging/msix/build-msix.ps1 index 0cc6738..58a343e 100644 --- a/packaging/msix/build-msix.ps1 +++ b/packaging/msix/build-msix.ps1 @@ -93,12 +93,15 @@ Write-Host "Copying application files..." -ForegroundColor Cyan Copy-Item -Path "$ExeDir\*" -Destination $StageDir -Recurse -Force # Copy AppxManifest and update version +# Use XML DOM patching (not regex) to avoid corrupting MinVersion/MaxVersionTested $ManifestPath = Join-Path $ScriptDir "AppxManifest.xml" $ManifestDest = Join-Path $StageDir "AppxManifest.xml" Copy-Item -Path $ManifestPath -Destination $ManifestDest -Force -$content = [System.IO.File]::ReadAllText("$ManifestDest") -$content = $content -creplace 'Version="[^"]+"', 'Version="' + "$Version" + '"' -[System.IO.File]::WriteAllText("$ManifestDest", $content, [System.Text.UTF8Encoding]$false) +$manifestXml = New-Object System.Xml.XmlDocument +$manifestXml.PreserveWhitespace = $true +$manifestXml.Load($ManifestDest) +$manifestXml.Package.Identity.Version = $Version +$manifestXml.Save($ManifestDest) # Generate MSIX asset placeholders using Python Write-Host "Generating MSIX asset placeholders..." -ForegroundColor Cyan