| 1 | name: "Release Siti AI Windows Microsoft Store" |
| 2 | |
| 3 | on: |
| 4 | push: |
| 5 | branches: |
| 6 | - release/windows-microsoft-store |
| 7 | workflow_dispatch: |
| 8 | |
| 9 | # Push to the release branch above to build and draft-release the Windows |
| 10 | # Microsoft Store artifacts. The .msi/.exe are for direct download; the MSIX is |
| 11 | # what gets submitted to Partner Center (the Store re-signs it), which is the |
| 12 | # only form that carries the package identity Store services need. |
| 13 | jobs: |
| 14 | publish-tauri: |
| 15 | permissions: |
| 16 | contents: write |
| 17 | runs-on: windows-latest |
| 18 | # Job-level env so the MSIX steps' own `if:` conditions can read these — a |
| 19 | # step-level env is not in scope for that same step's `if`. When the secrets |
| 20 | # are absent the MSIX steps skip and the unsigned .msi/.exe still ship. |
| 21 | env: |
| 22 | MSIX_PUBLISHER: ${{ secrets.MSIX_PUBLISHER }} |
| 23 | MSIX_IDENTITY_NAME: ${{ secrets.MSIX_IDENTITY_NAME }} |
| 24 | steps: |
| 25 | - uses: actions/checkout@v7 |
| 26 | |
| 27 | - name: setup node |
| 28 | uses: actions/setup-node@v7 |
| 29 | with: |
| 30 | node-version: 22 |
| 31 | |
| 32 | # No version pin: reads the exact pnpm from package.json's |
| 33 | # packageManager field (pnpm@11.8.0), matching ci.yml. |
| 34 | - uses: pnpm/action-setup@v6 |
| 35 | |
| 36 | - name: Install Rust stable |
| 37 | uses: dtolnay/rust-toolchain@stable |
| 38 | |
| 39 | # The full dependency tree build is slow cold. Cache the cargo registry |
| 40 | # and target/ keyed on Cargo.lock so a repeat run (a manifest tweak, |
| 41 | # another Partner Center validation round) restores in minutes instead of |
| 42 | # rebuilding from scratch. |
| 43 | - name: Cache Rust build |
| 44 | uses: Swatinem/rust-cache@v2 |
| 45 | with: |
| 46 | workspaces: src-tauri |
| 47 | |
| 48 | # Validate the MSIX manifest before the long Rust build rather than after |
| 49 | # it. makeappx checks the manifest against its schema regardless of what |
| 50 | # the payload files contain, so a stub exe is enough to catch a schema |
| 51 | # error in seconds instead of after a full build. Skipped when the MSIX |
| 52 | # secrets are absent so the .msi/.exe build still ships. |
| 53 | - name: Validate MSIX manifest (fast fail) |
| 54 | if: ${{ env.MSIX_PUBLISHER != '' && env.MSIX_IDENTITY_NAME != '' }} |
| 55 | shell: pwsh |
| 56 | run: ./scripts/build-msix.ps1 -ValidateOnly |
| 57 | |
| 58 | - name: Install frontend dependencies |
| 59 | run: pnpm install --frozen-lockfile |
| 60 | |
| 61 | - name: Patch src-tauri/.cargo/config.toml for native Windows build |
| 62 | shell: pwsh |
| 63 | run: | |
| 64 | # The .cargo/config.toml is authored for macOS hosts: the [env] |
| 65 | # section force-sets CC/CXX to /usr/bin/clang and a macOS deployment |
| 66 | # target, none of which exist on this runner. Strip that section so |
| 67 | # Rust falls back to the runner's native MSVC toolchain. The |
| 68 | # per-Apple-target and Android sections are inert here and left alone. |
| 69 | $path = "src-tauri/.cargo/config.toml" |
| 70 | if (Test-Path $path) { |
| 71 | $lines = Get-Content $path |
| 72 | $out = [System.Collections.Generic.List[string]]::new() |
| 73 | $skipSection = $false |
| 74 | |
| 75 | foreach ($line in $lines) { |
| 76 | if ($line -match '^\[env\]') { |
| 77 | $skipSection = $true |
| 78 | continue |
| 79 | } |
| 80 | if ($skipSection -and $line -match '^\[') { |
| 81 | $skipSection = $false |
| 82 | } |
| 83 | if (-not $skipSection) { |
| 84 | $out.Add($line) |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | $out | Set-Content $path -Encoding UTF8 |
| 89 | Write-Host "Patched src-tauri/.cargo/config.toml — removed macOS-only [env] section" |
| 90 | } else { |
| 91 | Write-Host "No src-tauri/.cargo/config.toml to patch" |
| 92 | } |
| 93 | |
| 94 | - name: Setup MSVC developer environment |
| 95 | uses: ilammy/msvc-dev-cmd@v1 |
| 96 | |
| 97 | - name: Build App |
| 98 | shell: pwsh |
| 99 | run: pnpm tauri build -c src-tauri/tauri.prod-windows-microsoftstore.conf.json |
| 100 | |
| 101 | # The .msi/.exe above are for direct download only. A Store MSI/EXE |
| 102 | # submission is just a hosted installer URL, so it never gets package |
| 103 | # identity and Windows.Services.Store add-ons would fail from it. The MSIX |
| 104 | # below is what goes to Partner Center. It is deliberately left unsigned: |
| 105 | # the Store re-signs with the publisher cert. Skipped when the MSIX |
| 106 | # secrets are absent. |
| 107 | - name: Pack MSIX for Partner Center |
| 108 | if: ${{ env.MSIX_PUBLISHER != '' && env.MSIX_IDENTITY_NAME != '' }} |
| 109 | shell: pwsh |
| 110 | run: ./scripts/build-msix.ps1 |
| 111 | |
| 112 | - name: Extract version from tauri.prod-windows-microsoftstore.conf.json |
| 113 | id: get_version |
| 114 | shell: pwsh |
| 115 | run: | |
| 116 | $json = Get-Content src-tauri/tauri.prod-windows-microsoftstore.conf.json | ConvertFrom-Json |
| 117 | echo "VERSION=$($json.version)" >> $env:GITHUB_ENV |
| 118 | |
| 119 | - name: Release |
| 120 | uses: softprops/action-gh-release@v2 |
| 121 | with: |
| 122 | draft: true |
| 123 | prerelease: false |
| 124 | body: "See the assets to download this version and install." |
| 125 | name: "Siti AI v${{ env.VERSION }} (Windows Microsoft Store)" |
| 126 | files: | |
| 127 | src-tauri/target/release/bundle/msi/*.msi |
| 128 | src-tauri/target/release/bundle/nsis/*.exe |
| 129 | src-tauri/target/release/bundle/msix/*.msix |