Skip to content

Add MSIX packaging for Microsoft Store submission #19

Add MSIX packaging for Microsoft Store submission

Add MSIX packaging for Microsoft Store submission #19

Workflow file for this run

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/*