Add MSIX packaging for Microsoft Store submission #19
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| jobs: | |
| build: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [windows-latest, macos-latest] | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Install dependencies | |
| run: python -m pip install --upgrade pip && pip install -r requirements-dev.txt | |
| - name: Run tests | |
| run: python -m pytest tests/ -v | |
| - name: Build with PyInstaller | |
| run: pyinstaller packaging/build_exe.spec --noconfirm --distpath dist --workpath build | |
| # Packages dist/EasyPostDesktop/ (the PyInstaller output above) into | |
| # dist/EasyPostDesktop.msix for Microsoft Store submission via Partner | |
| # Center — see packaging/build_msix.py and README. | |
| - name: Build MSIX package | |
| if: matrix.os == 'windows-latest' | |
| run: python packaging\build_msix.py | |
| # MSIX/AppX packages must carry *some* signature to be structurally | |
| # valid, but the Microsoft Store re-signs with its own certificate | |
| # during publishing — a self-signed one is accepted for submission, so | |
| # this generates and discards a throwaway cert per run rather than | |
| # needing a purchased cert or any stored secret. See: | |
| # https://learn.microsoft.com/windows/apps/publish/publish-your-app/msix/app-package-requirements | |
| - name: Sign MSIX package | |
| if: matrix.os == 'windows-latest' | |
| shell: pwsh | |
| run: | | |
| $cert = New-SelfSignedCertificate -Type Custom -KeyUsage DigitalSignature ` | |
| -CertStoreLocation "Cert:\CurrentUser\My" ` | |
| -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}") ` | |
| -Subject "CN=A7D4B6C0-27D4-4F66-82EB-82F5DD466788" | |
| $password = ConvertTo-SecureString -String ([System.Guid]::NewGuid().ToString("N")) -Force -AsPlainText | |
| Export-PfxCertificate -Cert $cert -FilePath msix_signing.pfx -Password $password | Out-Null | |
| $plainPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto( | |
| [Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)) | |
| $signtool = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe" | | |
| Sort-Object FullName -Descending | Select-Object -First 1 | |
| & $signtool.FullName sign /fd SHA256 /a /f msix_signing.pfx /p $plainPassword dist\EasyPostDesktop.msix | |
| Remove-Item msix_signing.pfx | |
| Remove-Item "Cert:\CurrentUser\My\$($cert.Thumbprint)" | |
| # Unsigned builds still trigger SmartScreen/Gatekeeper warnings on first | |
| # run regardless of this step — only a paid code-signing certificate | |
| # (see README) actually removes that. This just gives users a way to | |
| # verify the download wasn't tampered with in transit. | |
| - name: Generate SHA256 checksums | |
| shell: python | |
| run: | | |
| import hashlib | |
| from pathlib import Path | |
| dist = Path("dist") | |
| lines = [] | |
| for path in sorted(dist.rglob("*")): | |
| if path.is_file(): | |
| digest = hashlib.sha256(path.read_bytes()).hexdigest() | |
| lines.append(f"{digest} {path.relative_to(dist)}") | |
| Path("dist/SHA256SUMS.txt").write_text("\n".join(lines) + "\n") | |
| # No-op unless WINDOWS_CODE_SIGNING_CERT_BASE64/_PASSWORD secrets are | |
| # set on the repo. Wire these up if a certificate is purchased later | |
| # (see README's "Windows SmartScreen warning" section) — everything | |
| # else in this workflow already works either way. | |
| - name: Sign Windows build | |
| if: matrix.os == 'windows-latest' && env.HAS_SIGNING_CERT == 'true' | |
| env: | |
| HAS_SIGNING_CERT: ${{ secrets.WINDOWS_CODE_SIGNING_CERT_BASE64 != '' }} | |
| shell: pwsh | |
| run: | | |
| $bytes = [Convert]::FromBase64String("${{ secrets.WINDOWS_CODE_SIGNING_CERT_BASE64 }}") | |
| [IO.File]::WriteAllBytes("cert.pfx", $bytes) | |
| $signtool = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe" | | |
| Sort-Object FullName -Descending | Select-Object -First 1 | |
| & $signtool.FullName sign /f cert.pfx /p "${{ secrets.WINDOWS_CODE_SIGNING_CERT_PASSWORD }}" ` | |
| /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 ` | |
| dist\EasyPostDesktop\EasyPostDesktop.exe | |
| Remove-Item cert.pfx | |
| - name: Upload build artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: EasyPostDesktop-${{ matrix.os }} | |
| path: dist/* |