diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ceabca47..76506f6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -786,9 +786,12 @@ jobs: - name: Build the web UI run: npm run build + # --msix packs the same directory a second time as the Store package + # (GDK-1380) — one compile, two outputs; the Windows job is CI's + # critical path (GDK-1036) and a second build would double it. - name: Pack the Windows app shell: pwsh - run: ./desktop/build-windows.ps1 + run: ./desktop/build-windows.ps1 --msix - name: The packed tree carries both binaries shell: pwsh @@ -817,6 +820,60 @@ jobs: working-directory: desktop run: go test ./... -run 'Protocol' -count=1 + # makeappx validates the manifest schema; only an install proves the + # identity, the full-trust entry point and the protocol declaration + # are accepted. The Store re-signs with its own certificate, so here + # a throwaway self-signed certificate with the same Publisher CN + # stands in — trusted by this runner only, for the length of the step. + # Last in the job on purpose: deploying a package that declares + # windows.protocol registers the scheme in this user's hive, and the + # HKCU assertion above must keep measuring the pack script alone. The + # GUI is not launched. + - name: The msix installs under its declared identity + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $msix = Get-ChildItem -Path desktop/build -File -Filter 'Gadak-*-windows-x64.msix' | Select-Object -First 1 + if (-not $msix) { throw "build-windows.ps1 --msix produced no .msix" } + $manifest = Select-Xml -Path desktop/msix/AppxManifest.xml -XPath '/*[local-name()="Package"]/*[local-name()="Identity"]' | Select-Object -First 1 + $publisher = $manifest.Node.Publisher + $name = $manifest.Node.Name + $kit = Get-ChildItem -Path "${env:ProgramFiles(x86)}\Windows Kits\10\bin" -Directory -Filter '10.*' | + Sort-Object { [version]$_.Name } -Descending | Select-Object -First 1 + $signtool = Join-Path $kit.FullName 'x64\signtool.exe' + if (-not (Test-Path -LiteralPath $signtool)) { throw "no signtool.exe under $($kit.FullName)" } + $cert = New-SelfSignedCertificate -Type Custom -Subject $publisher -KeyUsage DigitalSignature ` + -FriendlyName 'gadak ci throwaway' -CertStoreLocation 'Cert:\CurrentUser\My' ` + -TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.3', '2.5.29.19={text}') + $pfx = Join-Path $env:RUNNER_TEMP 'ci-throwaway.pfx' + $pw = ConvertTo-SecureString -String 'ci' -Force -AsPlainText + Export-PfxCertificate -Cert $cert -FilePath $pfx -Password $pw | Out-Null + # Sign a copy: the unsigned original is the Store upload artifact. + $test = Join-Path $env:RUNNER_TEMP $msix.Name + Copy-Item -LiteralPath $msix.FullName -Destination $test -Force + & $signtool sign /fd SHA256 /a /f $pfx /p ci $test + if ($LASTEXITCODE -ne 0) { throw "signtool exited $LASTEXITCODE" } + Import-PfxCertificate -FilePath $pfx -CertStoreLocation 'Cert:\LocalMachine\TrustedPeople' -Password $pw | Out-Null + Add-AppxPackage -Path $test + $pkg = Get-AppxPackage -Name $name + if (-not $pkg) { throw "installed, but Get-AppxPackage $name found nothing" } + Write-Host ("installed {0} {1} ({2})" -f $pkg.Name, $pkg.Version, $pkg.Architecture) + if ($pkg.Publisher -ne $publisher) { throw "publisher drifted: $($pkg.Publisher) vs manifest $publisher" } + $installed = Join-Path $pkg.InstallLocation 'gadak-desktop.exe' + if (-not (Test-Path -LiteralPath $installed)) { throw "package has no gadak-desktop.exe at $installed" } + Remove-AppxPackage -Package $pkg.PackageFullName + Write-Host "msix install/uninstall — ok" + exit 0 + + # The file Partner Center takes. The install check above signed a copy + # in RUNNER_TEMP, so this is the untouched unsigned package. + - name: Upload Store package artifact + uses: actions/upload-artifact@v4 + with: + name: gadak-desktop-msix-x64 + path: desktop/build/Gadak-*-windows-x64.msix + if-no-files-found: error + # In a child process, not in this step's own runspace: a non-zero # $LASTEXITCODE left behind at the end of a pwsh step is what the runner # reads as the step's result, so checking it in place fails the job even diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index b13ee064..cf5df4a9 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -284,12 +284,15 @@ jobs: if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } if (-not (Test-Path -LiteralPath dist/app)) { throw "dist/app missing" } - - name: Pack Windows portable zips + # --msix adds the unsigned Store package next to each zip (GDK-1380). + # It is a workflow artifact only, never a Release asset: unsigned, it + # is not installable, and the Store re-signs it after certification. + - name: Pack Windows portable zips and Store packages shell: pwsh run: | $ErrorActionPreference = 'Stop' - ./desktop/build-windows.ps1 --arch x64 --archive - ./desktop/build-windows.ps1 --arch arm64 --archive + ./desktop/build-windows.ps1 --arch x64 --archive --msix + ./desktop/build-windows.ps1 --arch arm64 --archive --msix # Recurrence gate (same decision as the macOS job, 2026-08-15): # v0.14.0 apps match gadak-desktop--.zip exactly and would @@ -329,6 +332,16 @@ jobs: desktop/build/Gadak-*-windows-*.zip if-no-files-found: error + # For the maintainer to download and submit in Partner Center. The + # Release step below filters on *.zip and never sees these. + - name: Upload Store package artifact (unsigned msix) + uses: actions/upload-artifact@v4 + with: + name: gadak-desktop-msix + path: | + desktop/build/Gadak-*-windows-*.msix + if-no-files-found: error + - name: Attach zip to GitHub Release if: startsWith(github.ref, 'refs/tags/') env: diff --git a/CHANGELOG.ko.md b/CHANGELOG.ko.md index 39fa3cff..70f8da50 100644 --- a/CHANGELOG.ko.md +++ b/CHANGELOG.ko.md @@ -2,6 +2,15 @@ English · 한국어 — 영문이 원본이며, 번역은 영문과 함께 갱신됩니다(마지막 동기화 2026-09-03). +## Unreleased + +- Windows 앱에 Microsoft Store 패키지가 생겼다: `desktop/build-windows.ps1 + --msix` 가 zip 이 싣는 것과 같은 `gadak-desktop.exe` 를 무서명 `.msix` 로 + 묶고, 릴리스 워크플로가 그것을 Partner Center 용 아티팩트로 남긴다. Store 는 + 인증한 패키지를 자기 인증서로 재서명하는데, 그것이 Smart App Control 을 + 지나는 유일한 무료 경로다 — zip 은 그대로 무서명이고, 등록이 심사를 통과하기 + 전까지 "Store 에 있다"는 말은 어디에도 없다 ([GDK-1380]). + ## v0.20.0 — 2026-09-03 **셸이 창 안에 있고, 그 셸은 이슈의 셸입니다.** 터미널 도크가 이제 창 @@ -1028,6 +1037,7 @@ HTTP·sync·에이전트 계약을 담았습니다. [GDK-208]: https://gadak.dev/backlog/#/?ks=GDK-208 [GDK-209]: https://gadak.dev/backlog/#/?ks=GDK-209 [GDK-211]: https://gadak.dev/backlog/#/?ks=GDK-211 +[GDK-1380]: https://gadak.dev/backlog/#/?ks=GDK-1380 [GDK-213]: https://gadak.dev/backlog/#/?ks=GDK-213 [GDK-214]: https://gadak.dev/backlog/#/?ks=GDK-214 [GDK-215]: https://gadak.dev/backlog/#/?ks=GDK-215 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f309bd9..7bac41f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ English · 한국어 +## Unreleased + +- The Windows app has a Microsoft Store package: `desktop/build-windows.ps1 + --msix` packs the same `gadak-desktop.exe` the zip ships into an unsigned + `.msix`, and the release workflow keeps it as an artifact for Partner + Center. The Store re-signs what it certifies, which is the one free route + past Smart App Control — the zip stays unsigned and unchanged, and nothing + is "on the Store" until a listing passes ([GDK-1380]). + ## v0.20.0 — 2026-09-03 **The shell is in the window, and it is the issue's shell.** The terminal @@ -1074,6 +1083,7 @@ and the storage schema plus the HTTP, sync and agent contracts. [GDK-208]: https://gadak.dev/backlog/#/?ks=GDK-208 [GDK-209]: https://gadak.dev/backlog/#/?ks=GDK-209 [GDK-211]: https://gadak.dev/backlog/#/?ks=GDK-211 +[GDK-1380]: https://gadak.dev/backlog/#/?ks=GDK-1380 [GDK-213]: https://gadak.dev/backlog/#/?ks=GDK-213 [GDK-214]: https://gadak.dev/backlog/#/?ks=GDK-214 [GDK-215]: https://gadak.dev/backlog/#/?ks=GDK-215 diff --git a/Makefile b/Makefile index a4ac7e98..a37e8300 100644 --- a/Makefile +++ b/Makefile @@ -93,6 +93,7 @@ MEDIA_DIR := docs/media brand: node tools/brand/render.mjs bash tools/brand/mobile-icons.sh + bash tools/brand/msix-assets.sh bash tools/check-brand-icons.sh # media-mcp is deliberately not here: it needs vhs and a Claude Code login, diff --git a/desktop/README.md b/desktop/README.md index 708da256..001cb117 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -13,7 +13,7 @@ both so the rest of the tree can point here instead of restating either. | GOOS | Pack | Shipped on tag | `ApplicationLaunchedWithUrl` | | --- | --- | --- | --- | | darwin | `desktop/build-app.sh` → `Gadak--arm64.dmg` (signed/notarized) | yes | yes (Apple Event; argv is not applied) | -| windows | `desktop/build-windows.ps1` → `Gadak--windows-.zip` (unsigned; [GDK-211]) | yes (from 0.16) | yes when argv is exactly one `://` argument (wails `pkg/application/application_windows.go`); otherwise argv | +| windows | `desktop/build-windows.ps1` → `Gadak--windows-.zip` (unsigned; [GDK-211]); `--msix` → `.msix` for the Microsoft Store (unsigned upload, Store re-signs; [GDK-1380]) | yes (from 0.16); the msix is a workflow artifact, not a Release asset | yes when argv is exactly one `://` argument (wails `pkg/application/application_windows.go`); otherwise argv | | linux | `desktop/build-linux.sh` → AppDir / AppImage | no — from-source only | yes when argv is exactly one `://` argument (wails `pkg/application/application_linux.go`; wailsapp/wails#6000 landed in beta.10); otherwise argv | The in-app Jira/Confluence browse pane is still darwin-only (`embed_darwin.go`; other GOOS use the stub in `embed_other.go`). @@ -40,9 +40,19 @@ desktop/build-linux.sh --appimage # → desktop/build/Gadak--x86_64.A Windows (cross-compile with `CGO_ENABLED=0`; Authenticode and a missing-WebView2 check still need a Windows host — see WebView2 below): ```powershell -desktop/build-windows.ps1 # → desktop/build/Gadak--x64/ +desktop/build-windows.ps1 # → desktop/build/Gadak--x64/ +desktop/build-windows.ps1 --msix # + desktop/build/Gadak--windows-x64.msix (Windows host: makeappx) ``` +`--msix` stages that same directory with `desktop/msix/AppxManifest.xml` and +the committed `desktop/msix/Assets/` logos (regenerated by `make brand`) and +runs `makeappx pack`. The manifest version is `(major+1).minor.patch.0` +because Partner Center refuses a 0.x major — see the comment in the script; +that mapping is a one-way door once the Store has accepted a package. The +output is unsigned on purpose: the Store re-signs with a Microsoft +certificate after certification, so the file is for Partner Center, not for +users ([GDK-1380]). + The Windows directory name uses the Windows pack labels (`x64`, `arm64`), not the macOS dmg labels (`amd64`, `arm64`) or the AppImage / `uname -m` labels (`x86_64`, `aarch64`). All three scripts stamp the version from the same @@ -50,7 +60,8 @@ the macOS dmg labels (`amd64`, `arm64`) or the AppImage / `uname -m` labels `build-linux.sh` and `build-windows.ps1` exit 64 on usage / unknown arguments and 69 when a required tool is missing. For Windows that is `go` (and `git` -when Git Bash is not on `PATH` to eval the `version=` line). `build-linux.sh` +when Git Bash is not on `PATH` to eval the `version=` line), plus +`makeappx.exe` from the Windows SDK when `--msix` is passed. `build-linux.sh` also needs `pkg-config`, `cc`, `magick`/`convert`/`ffmpeg`, the `gtk4` and `webkitgtk-6.0` pkg-config modules, and `appimagetool` when `--appimage` is passed. @@ -119,7 +130,12 @@ Windows machine in this repository (the authoring runner is darwin). Required: Go, and `dist/app` from `npm run build` at the repo root. Git is required only when `bash` is missing, so the script can run the same -`git describe --tags --always` formula as `desktop/build-app.sh`. +`git describe --tags --always` formula as `desktop/build-app.sh`. `--msix` +additionally needs `makeappx.exe` (Windows SDK, found under +`Windows Kits\10\bin\\x64` when not on `PATH`); the CI Windows job +also signs the result with a throwaway self-signed certificate and installs +it, which is the only check that the identity and full-trust declaration are +accepted — `makeappx` alone validates the schema. ### WebView2 @@ -270,4 +286,5 @@ would try to self-swap. gone — wails v3 declares that framework itself.) [GDK-211]: https://midagedev.github.io/gadak/backlog/#/?ks=GDK-211 +[GDK-1380]: https://gadak.dev/backlog/#/?ks=GDK-1380 [GDK-293]: https://midagedev.github.io/gadak/backlog/#/?ks=GDK-293 diff --git a/desktop/build-windows.ps1 b/desktop/build-windows.ps1 index 105e727d..95ed862c 100644 --- a/desktop/build-windows.ps1 +++ b/desktop/build-windows.ps1 @@ -7,12 +7,21 @@ # What still needs Windows is Authenticode signing (GDK-211) and any # WebView2 runtime/bootstrap installer work; this script does neither. # -# Usage: desktop/build-windows.ps1 [--arch x64|arm64] [--archive] +# Usage: desktop/build-windows.ps1 [--arch x64|arm64] [--archive] [--msix] # # Exit 64 = usage / unknown argument -# 69 = a required tool is missing +# 69 = a required tool is missing (go; makeappx.exe for --msix) # 1 = dist/app missing or a build step failed -# 0 = portable directory written (and the zip, if --archive) +# 0 = portable directory written (and the zip / msix, if asked) +# +# --msix (GDK-1380) additionally packs the same directory into an UNSIGNED +# Gadak--windows-.msix for Microsoft Store submission, from +# desktop/msix/AppxManifest.xml and the committed desktop/msix/Assets/. It +# needs makeappx.exe (Windows SDK), so it is a Windows-host output — the +# portable directory and zip still cross-compile from anywhere. The Store +# re-signs the package with a Microsoft certificate after certification; +# the file this script writes is not installable as-is and is never a +# GitHub Release asset. # # This script must not emit gadak-desktop--.zip — v0.14.0 apps # match that name exactly and would self-swap (same rule as @@ -44,7 +53,7 @@ $ErrorActionPreference = 'Stop' function Usage { - [Console]::Error.WriteLine('usage: desktop/build-windows.ps1 [--arch x64|arm64] [--archive]') + [Console]::Error.WriteLine('usage: desktop/build-windows.ps1 [--arch x64|arm64] [--archive] [--msix]') exit 64 } @@ -57,12 +66,14 @@ function Need { } $wantArchive = $false +$wantMsix = $false $archArg = $null for ($i = 0; $i -lt $args.Count; $i++) { $arg = [string]$args[$i] switch ($arg) { { $_ -in @('-h', '--help') } { Usage } { $_ -in @('--archive', '-Archive', '-archive') } { $wantArchive = $true } + { $_ -in @('--msix', '-Msix', '-msix') } { $wantMsix = $true } { $_ -in @('--arch', '-Arch', '-arch') } { if ($i + 1 -ge $args.Count) { [Console]::Error.WriteLine('build-windows: --arch needs x64 or arm64') @@ -233,6 +244,93 @@ finally { Write-Host "built $bundle ($version, $fileArch)" +if ($wantMsix) { + # Store package version: four numeric parts, the fourth reserved (0) and + # the first non-zero — Partner Center refuses a 0.x major. gadak is 0.x, + # so the manifest carries (major+1).minor.patch.0: 0.20.0 → 1.20.0.0, + # and a future 1.0.0 → 2.0.0.0 stays monotonic, which matters because + # the Store rejects any upload lower than one it has accepted. A + # git-describe suffix (0.20.0-33-gabc) is dropped. A stamp with no + # semver at all — 0.0.0-dev, or the bare hash a shallow CI checkout + # without tags produces — yields 1.0.0.0 so the pack can still be built + # and install-tested; the Store never sees one of those, the release + # job checks out with tags. + # ponytail: the +1 is a one-way door once the first package is accepted; + # never "fix" it back to the semver major. + function Get-MsixVersion { + param([string]$Stamp) + if ($Stamp -notmatch '^(\d+)\.(\d+)\.(\d+)') { + [Console]::Error.WriteLine("build-windows: no semver in stamp '$Stamp'; manifest version 1.0.0.0 (untagged build, not for the Store)") + return '1.0.0.0' + } + return ('{0}.{1}.{2}.0' -f ([int]$Matches[1] + 1), [int]$Matches[2], [int]$Matches[3]) + } + + # makeappx.exe ships in the Windows SDK, not on PATH; take the newest kit. + $makeappx = Get-Command makeappx.exe -ErrorAction SilentlyContinue + if ($null -eq $makeappx) { + $kits = ${env:ProgramFiles(x86)} + if ([string]::IsNullOrEmpty($kits)) { $kits = $env:ProgramFiles } + $candidates = @() + if (-not [string]::IsNullOrEmpty($kits)) { + $candidates = @(Get-ChildItem -Path (Join-Path $kits 'Windows Kits\10\bin') -Directory -Filter '10.*' -ErrorAction SilentlyContinue | + Sort-Object { [version]$_.Name } -Descending | + ForEach-Object { Join-Path $_.FullName 'x64\makeappx.exe' } | + Where-Object { Test-Path -LiteralPath $_ }) + } + if ($candidates.Count -eq 0) { + [Console]::Error.WriteLine('build-windows: missing makeappx.exe (Windows SDK) — --msix needs a Windows host') + exit 69 + } + $makeappx = Get-Command $candidates[0] + } + + $msixDir = Join-Path $here 'msix' + $manifestSrc = Join-Path $msixDir 'AppxManifest.xml' + $assetsSrc = Join-Path $msixDir 'Assets' + foreach ($p in @($manifestSrc, $assetsSrc)) { + if (-not (Test-Path -LiteralPath $p)) { + [Console]::Error.WriteLine("build-windows: missing $p") + exit 1 + } + } + + # Stage = the portable directory + Assets + the substituted manifest. + # makeappx wants the manifest named exactly AppxManifest.xml at the root. + $stage = Join-Path $out ("msix-stage-{0}" -f $fileArch) + if (Test-Path -LiteralPath $stage) { + Remove-Item -LiteralPath $stage -Recurse -Force + } + New-Item -ItemType Directory -Path $stage | Out-Null + Copy-Item -Path (Join-Path $bundle '*') -Destination $stage -Recurse + Copy-Item -Path $assetsSrc -Destination (Join-Path $stage 'Assets') -Recurse + Remove-Item -LiteralPath (Join-Path $stage 'Assets\SOURCE.sha256') -Force -ErrorAction SilentlyContinue + $msixVersion = Get-MsixVersion -Stamp $verStamp + $manifest = Get-Content -LiteralPath $manifestSrc -Raw + $manifest = $manifest.Replace('@VERSION@', $msixVersion).Replace('@ARCH@', $fileArch) + if ($manifest -match '@[A-Z]+@') { + [Console]::Error.WriteLine("build-windows: unsubstituted placeholder in AppxManifest.xml: $($Matches[0])") + exit 1 + } + # UTF-8 without BOM: makeappx reads the declaration, not a BOM. + [System.IO.File]::WriteAllText((Join-Path $stage 'AppxManifest.xml'), $manifest, (New-Object System.Text.UTF8Encoding $false)) + + $msixPath = Join-Path $out ('Gadak-{0}-windows-{1}.msix' -f $verStamp, $fileArch) + if (Test-Path -LiteralPath $msixPath) { + Remove-Item -LiteralPath $msixPath -Force + } + & $makeappx.Source pack /d $stage /p $msixPath /o + if ($LASTEXITCODE -ne 0) { + [Console]::Error.WriteLine("build-windows: makeappx pack exited $LASTEXITCODE") + exit 1 + } + if (-not (Test-Path -LiteralPath $msixPath) -or ((Get-Item -LiteralPath $msixPath).Length -le 0)) { + [Console]::Error.WriteLine("build-windows: msix was not written: $msixPath") + exit 1 + } + Write-Host "packed $msixPath (manifest version $msixVersion, unsigned — Store re-signs)" +} + if (-not $wantArchive) { exit 0 } diff --git a/desktop/msix/AppxManifest.xml b/desktop/msix/AppxManifest.xml new file mode 100644 index 00000000..1e79bade --- /dev/null +++ b/desktop/msix/AppxManifest.xml @@ -0,0 +1,72 @@ + + + + + + + + Gadak + midagedev + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + Gadak + + + + + + + + + + diff --git a/desktop/msix/Assets/SOURCE.sha256 b/desktop/msix/Assets/SOURCE.sha256 new file mode 100644 index 00000000..4b061f57 --- /dev/null +++ b/desktop/msix/Assets/SOURCE.sha256 @@ -0,0 +1,4 @@ +# The brand source these logos were generated from. +# Regenerate: make brand (tools/brand/msix-assets.sh) +# Verify: bash tools/check-brand-icons.sh +docs/media/logo.png 0e6d51688db3f2fbd7c9bd494f460f27c232dc294f4d989c19898bff310264d3 diff --git a/desktop/msix/Assets/Square150x150Logo.png b/desktop/msix/Assets/Square150x150Logo.png new file mode 100644 index 00000000..24009154 Binary files /dev/null and b/desktop/msix/Assets/Square150x150Logo.png differ diff --git a/desktop/msix/Assets/Square150x150Logo.scale-200.png b/desktop/msix/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 00000000..35b1c7e6 Binary files /dev/null and b/desktop/msix/Assets/Square150x150Logo.scale-200.png differ diff --git a/desktop/msix/Assets/Square44x44Logo.png b/desktop/msix/Assets/Square44x44Logo.png new file mode 100644 index 00000000..dad235cb Binary files /dev/null and b/desktop/msix/Assets/Square44x44Logo.png differ diff --git a/desktop/msix/Assets/Square44x44Logo.scale-200.png b/desktop/msix/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 00000000..1f043591 Binary files /dev/null and b/desktop/msix/Assets/Square44x44Logo.scale-200.png differ diff --git a/desktop/msix/Assets/StoreLogo.png b/desktop/msix/Assets/StoreLogo.png new file mode 100644 index 00000000..60b697c1 Binary files /dev/null and b/desktop/msix/Assets/StoreLogo.png differ diff --git a/desktop/msix/Assets/StoreLogo.scale-200.png b/desktop/msix/Assets/StoreLogo.scale-200.png new file mode 100644 index 00000000..9e6e726f Binary files /dev/null and b/desktop/msix/Assets/StoreLogo.scale-200.png differ diff --git a/docs/WINDOWS-SIGNING.md b/docs/WINDOWS-SIGNING.md index d6b40093..419eb88c 100644 --- a/docs/WINDOWS-SIGNING.md +++ b/docs/WINDOWS-SIGNING.md @@ -20,6 +20,18 @@ enough download volume that publisher reputation would carry. Until then, the honest thing is a page that says which file you have and what the warning is, rather than a roadmap line promising a date. +**The first condition has since been met from a direction this page did not +list (2026-09-03, [GDK-1380]).** Microsoft Store developer registration is +free now, and the Store re-signs every MSIX it certifies with a Microsoft +certificate — no publisher certificate, and a Store-installed app is neither +a SmartScreen download nor an unknown signer to Smart App Control. So the +Windows app is being submitted there: `desktop/build-windows.ps1 --msix` +packs the same `gadak-desktop.exe` as the zip into an unsigned `.msix` for +Partner Center. Until a listing has passed certification, nothing below +changes — the GitHub Release zip stays unsigned and this page still describes +it. When it passes, this section becomes the install route for anyone Smart +App Control blocks, and the zip stays for everyone else. + This page is the place to check when Windows shows a warning. It is not a claim that the unsigned zip is “safe”. It is how to see **which file you have**, and what that warning actually is. @@ -238,3 +250,4 @@ would close the desktop digest gap without a certificate. That is a CI change and is not in this round. [GDK-211]: https://midagedev.github.io/gadak/backlog/#/?ks=GDK-211 +[GDK-1380]: https://gadak.dev/backlog/#/?ks=GDK-1380 diff --git a/tools/brand/msix-assets.sh b/tools/brand/msix-assets.sh new file mode 100755 index 00000000..fe6ac673 --- /dev/null +++ b/tools/brand/msix-assets.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Regenerates the Microsoft Store (MSIX) logo set from the brand source. +# +# The Windows runner that packs the .msix has no image tool we can rely on, +# so these PNGs are generated here and committed, the same way the phone +# icons are (tools/brand/mobile-icons.sh). AppxManifest.xml names the three +# base files; Windows picks the .scale-200 sibling on high-DPI displays. +# +# Usage: bash tools/brand/msix-assets.sh (or: make brand) +set -euo pipefail + +repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +src="$repo/docs/media/logo.png" +out="$repo/desktop/msix/Assets" + +sha256() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | cut -d' ' -f1 + else + shasum -a 256 "$1" | cut -d' ' -f1 + fi +} + +[ -f "$src" ] || { echo "missing brand source: $src (run: node tools/brand/render.mjs)" >&2; exit 1; } + +resize() { # resize + if command -v sips >/dev/null 2>&1; then + sips -z "$1" "$1" "$src" --out "$2" >/dev/null + elif command -v magick >/dev/null 2>&1; then + magick "$src" -resize "${1}x${1}" "$2" + else + echo "msix-assets: need sips (macOS) or ImageMagick" >&2 + exit 69 + fi +} + +mkdir -p "$out" +resize 150 "$out/Square150x150Logo.png" +resize 300 "$out/Square150x150Logo.scale-200.png" +resize 44 "$out/Square44x44Logo.png" +resize 88 "$out/Square44x44Logo.scale-200.png" +resize 50 "$out/StoreLogo.png" +resize 100 "$out/StoreLogo.scale-200.png" + +cat > "$out/SOURCE.sha256" <