diff options
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | packages/meshbay-client/build/appx-extensions.xml | 3 | ||||
| -rw-r--r-- | packages/meshbay-client/build/appx/README.md | 44 | ||||
| -rw-r--r-- | packages/meshbay-client/build/appx/Square150x150Logo.png | bin | 0 -> 38507 bytes | |||
| -rw-r--r-- | packages/meshbay-client/build/appx/Square44x44Logo.png | bin | 0 -> 4258 bytes | |||
| -rw-r--r-- | packages/meshbay-client/build/appx/StoreLogo.png | bin | 0 -> 5388 bytes | |||
| -rw-r--r-- | packages/meshbay-client/build/appx/Wide310x150Logo.png | bin | 0 -> 32069 bytes | |||
| -rw-r--r-- | packages/meshbay-client/package.json | 5 | ||||
| -rw-r--r-- | packages/meshbay-node/tests/test_packaging_win.py | 204 | ||||
| -rw-r--r-- | packaging/win/build-win-msix.ps1 | 134 | ||||
| -rw-r--r-- | packaging/win/electron-builder.msix.yml | 113 |
11 files changed, 499 insertions, 7 deletions
@@ -67,6 +67,9 @@ packages/meshbay-client/node-runtime/ # directories.output) -- the bare `dist/` rule above does not match this # different name, so it needs its own line. packages/meshbay-client/dist-light/ +# build-win-msix.ps1's own output dir (electron-builder.msix.yml +# directories.output) -- same reasoning as dist-light/ above. +packages/meshbay-client/dist-msix/ # electron-builder's buildResources dir — a real source (app icon), not a # build artifact, despite living under a path the generic `build/` rule above diff --git a/packages/meshbay-client/build/appx-extensions.xml b/packages/meshbay-client/build/appx-extensions.xml new file mode 100644 index 0000000..1ee1cca --- /dev/null +++ b/packages/meshbay-client/build/appx-extensions.xml @@ -0,0 +1,3 @@ + <desktop:Extension Category="windows.startupTask" Executable="app\resources\node-runtime\meshbay-node.exe" EntryPoint="Windows.FullTrustApplication"> + <desktop:StartupTask TaskId="MeshBayNodeStartup" Enabled="true" DisplayName="MeshBay Node" /> + </desktop:Extension> diff --git a/packages/meshbay-client/build/appx/README.md b/packages/meshbay-client/build/appx/README.md new file mode 100644 index 0000000..ef47411 --- /dev/null +++ b/packages/meshbay-client/build/appx/README.md @@ -0,0 +1,44 @@ +# AppX/MSIX tile images + +Generated once from `../icon-square.png` (1024×1024), not by any build script +— electron-builder's AppX target (`app-builder-lib/out/targets/AppxTarget.js`) +requires these four regardless of `showNameOnTiles`, and looks for them here +(`packager.getResource(undefined, "appx")` resolves to `build/appx/`) before +falling back to its own vendor-bundled samples. Falling back was not an +option here: this repo points `ELECTRON_BUILDER_WINDOWS_KITS_PATH` at the +system Windows SDK instead of letting electron-builder download its own +copy (see `packaging/win/build-win-msix.ps1`'s comment for why), and the +system SDK has no `appxAssets` samples to fall back to at all. + +| File | Size | Source | +|---|---|---| +| `StoreLogo.png` | 50×50 | resized | +| `Square44x44Logo.png` | 44×44 | resized | +| `Square150x150Logo.png` | 150×150 | resized | +| `Wide310x150Logo.png` | 310×150 | icon centered on a `#464646` canvas (the appx target's own default `backgroundColor`) | + +Regenerate with System.Drawing if the source icon changes: + +```powershell +Add-Type -AssemblyName System.Drawing +$img = [System.Drawing.Image]::FromFile("build\icon-square.png") +function Resize-Square($image, $size, $outPath) { + $bmp = New-Object System.Drawing.Bitmap $size, $size + $g = [System.Drawing.Graphics]::FromImage($bmp) + $g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic + $g.DrawImage($image, 0, 0, $size, $size) + $bmp.Save($outPath, [System.Drawing.Imaging.ImageFormat]::Png) + $g.Dispose(); $bmp.Dispose() +} +Resize-Square $img 50 "build\appx\StoreLogo.png" +Resize-Square $img 44 "build\appx\Square44x44Logo.png" +Resize-Square $img 150 "build\appx\Square150x150Logo.png" + +$bmp = New-Object System.Drawing.Bitmap 310, 150 +$g = [System.Drawing.Graphics]::FromImage($bmp) +$g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic +$g.Clear([System.Drawing.Color]::FromArgb(255, 0x46, 0x46, 0x46)) +$g.DrawImage($img, 90, 10, 130, 130) +$bmp.Save("build\appx\Wide310x150Logo.png", [System.Drawing.Imaging.ImageFormat]::Png) +$g.Dispose(); $bmp.Dispose(); $img.Dispose() +``` diff --git a/packages/meshbay-client/build/appx/Square150x150Logo.png b/packages/meshbay-client/build/appx/Square150x150Logo.png Binary files differnew file mode 100644 index 0000000..cd64829 --- /dev/null +++ b/packages/meshbay-client/build/appx/Square150x150Logo.png diff --git a/packages/meshbay-client/build/appx/Square44x44Logo.png b/packages/meshbay-client/build/appx/Square44x44Logo.png Binary files differnew file mode 100644 index 0000000..d714c94 --- /dev/null +++ b/packages/meshbay-client/build/appx/Square44x44Logo.png diff --git a/packages/meshbay-client/build/appx/StoreLogo.png b/packages/meshbay-client/build/appx/StoreLogo.png Binary files differnew file mode 100644 index 0000000..2062b36 --- /dev/null +++ b/packages/meshbay-client/build/appx/StoreLogo.png diff --git a/packages/meshbay-client/build/appx/Wide310x150Logo.png b/packages/meshbay-client/build/appx/Wide310x150Logo.png Binary files differnew file mode 100644 index 0000000..0ca324d --- /dev/null +++ b/packages/meshbay-client/build/appx/Wide310x150Logo.png diff --git a/packages/meshbay-client/package.json b/packages/meshbay-client/package.json index 614e6d6..6d8e711 100644 --- a/packages/meshbay-client/package.json +++ b/packages/meshbay-client/package.json @@ -1,7 +1,7 @@ { "name": "meshbay-client", "version": "0.13.0", - "description": "MeshBay desktop client \u2014 the interface ships with the application, not from the hub", + "description": "MeshBay desktop client — the interface ships with the application, not from the hub", "license": "AGPL-3.0-or-later", "author": "MeshBay Team <team@meshbay.org>", "homepage": "https://meshbay.org", @@ -12,7 +12,8 @@ "sync-ui": "node scripts/sync-ui.js", "dist": "bash ../../packaging/build/build-client.sh", "dist:win": "powershell -NoProfile -ExecutionPolicy Bypass -File ../../packaging/win/build-win.ps1", - "dist:win:light": "powershell -NoProfile -ExecutionPolicy Bypass -File ../../packaging/win/build-win-light.ps1" + "dist:win:light": "powershell -NoProfile -ExecutionPolicy Bypass -File ../../packaging/win/build-win-light.ps1", + "dist:win:msix": "powershell -NoProfile -ExecutionPolicy Bypass -File ../../packaging/win/build-win-msix.ps1" }, "devDependencies": { "electron": "^44.2.0", diff --git a/packages/meshbay-node/tests/test_packaging_win.py b/packages/meshbay-node/tests/test_packaging_win.py index b223156..2cc8f12 100644 --- a/packages/meshbay-node/tests/test_packaging_win.py +++ b/packages/meshbay-node/tests/test_packaging_win.py @@ -31,6 +31,13 @@ LIGHT_YML = WIN / "electron-builder.light.yml" BUILD_WIN_LIGHT = WIN / "build-win-light.ps1" BUILD_WIN_COMMON = WIN / "build-win-common.ps1" +# The "MSIX" target: same feature set as Full, packaged for Microsoft Store +# submission instead of NSIS. See C:\Users\admin\devel\msix-installer.md for +# the plan this implements. +MSIX_YML = WIN / "electron-builder.msix.yml" +BUILD_WIN_MSIX = WIN / "build-win-msix.ps1" +MSIX_EXTENSIONS_XML = CLIENT / "build" / "appx-extensions.xml" + # sync-ui.js copies this into CLIENT/ui/ verbatim -- read the source of # truth, same as every other cross-package check in this file already does # for meshbay-client's own src/. @@ -746,10 +753,10 @@ def test_build_win_light_skips_the_node_runtime_step(): def test_build_orchestrators_share_the_common_steps_not_a_copy(): """ Node check / npm ci / Electron bump / sync-ui must live in exactly one - place (build-win-common.ps1), dot-sourced by both -- a copy would let - the two drift the way the installer flow itself once did (the W3 - one-shot dialog bug). Both orchestrators must call the shared - functions, neither may inline its own npm ci / Electron-bump logic. + place (build-win-common.ps1), dot-sourced by all three orchestrators -- + a copy would let them drift the way the installer flow itself once did + (the W3 one-shot dialog bug). Every orchestrator must call the shared + functions, none may inline its own npm ci / Electron-bump logic. """ assert BUILD_WIN_COMMON.exists(), f"{BUILD_WIN_COMMON} is missing" common = BUILD_WIN_COMMON.read_text(encoding="utf-8") @@ -758,7 +765,9 @@ def test_build_orchestrators_share_the_common_steps_not_a_copy(): full = (WIN / "build-win.ps1").read_text(encoding="utf-8") light = BUILD_WIN_LIGHT.read_text(encoding="utf-8") - for src, name in ((full, "build-win.ps1"), (light, "build-win-light.ps1")): + msix = BUILD_WIN_MSIX.read_text(encoding="utf-8") + for src, name in ((full, "build-win.ps1"), (light, "build-win-light.ps1"), + (msix, "build-win-msix.ps1")): assert '. (Join-Path $WinDir "build-win-common.ps1")' in src, ( f"{name} does not dot-source build-win-common.ps1") for fn in ("Assert-NodeVersion", "Invoke-NpmCi", "Invoke-ElectronBump", "Invoke-SyncUi"): @@ -908,3 +917,188 @@ def test_create_group_page_falls_back_when_no_node_is_bundled(): assert "platform.node.bundled()" in body assert "platform.node.available && bundled" in body assert "CreateGroupWizard" in body and "CreateGroupFormSimple" in body + + +# ------------------------------------------------------------------------ +# The "MSIX" target: same feature set as Full, packaged for Microsoft Store +# submission instead of NSIS. See C:\Users\admin\devel\msix-installer.md for +# the plan. Unlike Light, this target keeps the node runtime and both +# service scripts -- what changes is packaging format, not what ships. +# Weak, text-reading evidence throughout, same reasoning as the rest of +# this file: there is no electron-builder/appx runner here either. +# ------------------------------------------------------------------------ + +def test_msix_config_is_standalone_and_keeps_the_full_bundle(): + """ + Unlike Light, MSIX ships the same node-runtime/ffmpeg/service scripts as + Full -- an AppX install never elevating (msix-installer.md 4) is not a + reason to drop the daemon, only to change how its two elevated + operations get triggered (see the two tests below). --config still + means this file is read alone (app-builder-lib's getConfig), so it + cannot silently inherit Full's package.json build.nsis or any signing + config meant for NSIS. + """ + assert MSIX_YML.exists(), f"{MSIX_YML} is missing" + yml = MSIX_YML.read_text(encoding="utf-8") + + assert "appId: org.meshbay.client" in yml + assert "target: appx" in yml + assert "output: dist-msix" in yml + + assert "node-runtime" in yml + assert "service.ps1" in yml + assert "service-mode.ps1" in yml + assert "firewall.ps1" in yml + + +def test_msix_identity_matches_the_partner_center_reservation(): + """ + identityName/publisher/publisherDisplayName are assigned by Partner + Center's "App identity" page when the app name is reserved -- they + cannot be chosen freely, and a mismatch fails Store validation outright + rather than warning. Pins the exact reservation (done 2026-09-11) so a + future edit cannot silently drift back to electron-builder's own + unconfigured-build stand-in ("CN=ms", see windowsSignToolManager.js's + computePublisherName) without a test noticing. + publisherDisplayName is "MeshBay" as Partner Center assigned it, NOT + package.json's author company name ("MeshBay Team") -- AppXOptions.d.ts's + own default (company name from app metadata) would pick the wrong one if + this were ever left unset instead of explicit. + """ + yml = MSIX_YML.read_text(encoding="utf-8") + assert "identityName: MeshBay.MeshBay" in yml + assert 'publisher: "CN=CE32BB0D-6B7C-4D3A-AA42-E259B778CAC9"' in yml + assert "publisherDisplayName: MeshBay" in yml + assert '"CN=ms"' not in yml, ( + "must not have drifted back to electron-builder's own placeholder " + "publisher") + + +def test_msix_declares_no_csc_on_purpose(): + """ + No certificateFile/certificateSubjectName/certificateSha1 anywhere in + this config -- per app-builder-lib's own windowsSignToolManager.js, an + AppX target built with no certificate configured is logged as "Windows + Store only build" and left unsigned; Microsoft signs it at publish time + (msix-installer.md 3). Configuring a cert here would be wasted work, not + extra safety, and would risk this target picking up whatever might one + day be configured for Full's NSIS signing if it were ever added to this + file instead of package.json's own build.win. + """ + yml = MSIX_YML.read_text(encoding="utf-8") + for forbidden in ("certificateFile", "certificateSubjectName", "certificateSha1"): + assert forbidden not in yml + + +def test_msix_declares_the_network_capabilities_firewall_ps1_would_add(): + """ + Matches firewall.ps1's own rules, which are `-Profile Any` (private AND + public network) -- msix-installer.md 8's #1 open item: whether Windows + actually auto-exempts a full-trust packaged app on the strength of + these declarations is unverified until sideloaded, but the declaration + itself must at least match what the elevated NSIS path grants today, or + an MSIX install would be silently narrower than Full/Light. + """ + yml = MSIX_YML.read_text(encoding="utf-8") + caps_block = yml.split("capabilities:", 1)[1].split("customExtensionsPath", 1)[0] + assert "internetClientServer" in caps_block + assert "privateNetworkClientServer" in caps_block + + +def test_msix_startup_task_targets_the_node_not_the_electron_shell(): + """ + addAutoLaunchExtension's built-in windows.startupTask (app-builder-lib's + AppxTarget.js) always targets the package's own main executable -- the + Electron shell -- which is not what "starts at sign in" means today + (main.js's WIN_STARTUP_VBS launches meshbay-node.exe directly, keeping + the daemon running whether or not the UI is ever opened). This config + must NOT use addAutoLaunchExtension for that reason, and must instead + supply its own extension via customExtensionsPath pointing at the node + binary's in-package path -- app\\resources\\node-runtime\\meshbay-node.exe, + derived from AppxTarget.js's own `"app\\\\" + appOutDir-relative path` + mapping (build() in that file), which is not the same prefix + process.resourcesPath resolves to at runtime and easy to get wrong. + """ + yml = MSIX_YML.read_text(encoding="utf-8") + # The comment explaining *why* addAutoLaunchExtension is not used + # necessarily names it -- check the directive, not the prose (the same + # "parse directives, not text" mistake CLAUDE.md's engineering lessons + # already record for a differently-shaped bug). + lines = [ln.strip() for ln in yml.splitlines()] + assert not any(ln.startswith("addAutoLaunchExtension:") for ln in lines), ( + "must not set addAutoLaunchExtension -- it always targets the " + "Electron shell, not the node binary (see this test's docstring)") + assert "customExtensionsPath: build/appx-extensions.xml" in yml + + assert MSIX_EXTENSIONS_XML.exists(), f"{MSIX_EXTENSIONS_XML} is missing" + ext = MSIX_EXTENSIONS_XML.read_text(encoding="utf-8") + assert 'Category="windows.startupTask"' in ext + assert 'Executable="app\\resources\\node-runtime\\meshbay-node.exe"' in ext + assert 'EntryPoint="Windows.FullTrustApplication"' in ext + + +def test_msix_ships_the_four_required_tile_images(): + """ + app-builder-lib's AppxTarget.js requires StoreLogo/Square44x44Logo/ + Square150x150Logo/Wide310x150Logo regardless of showNameOnTiles, falling + back to its own vendor-bundled samples if build/appx/ does not supply + them. This repo points ELECTRON_BUILDER_WINDOWS_KITS_PATH at the system + Windows SDK instead of that vendor bundle (build-win-msix.ps1), which + has no samples to fall back to -- so a missing one here is a hard + makeappx failure, not a cosmetic gap. See build/appx/README.md for + where these came from. + """ + appx_assets = CLIENT / "build" / "appx" + for name in ("StoreLogo.png", "Square44x44Logo.png", + "Square150x150Logo.png", "Wide310x150Logo.png"): + assert (appx_assets / name).exists(), f"{name} is missing from {appx_assets}" + + +def test_build_win_msix_keeps_the_node_runtime_step(): + """The opposite of Light's equivalent test: MSIX is Full's feature set, + repackaged -- build-node-runtime.ps1 must still run.""" + assert BUILD_WIN_MSIX.exists(), f"{BUILD_WIN_MSIX} is missing" + src = BUILD_WIN_MSIX.read_text(encoding="utf-8") + assert "build-node-runtime.ps1" in src + assert "electron-builder.msix.yml" in src + assert "--config" in src + assert "--win appx" in src + + +def test_dist_win_msix_delegates_to_the_msix_orchestrator(): + pkg = _pkg() + scripts = pkg["scripts"] + assert "dist:win:msix" in scripts + assert "build-win-msix.ps1" in scripts["dist:win:msix"] + + +def test_build_win_msix_points_electron_builder_at_the_system_sdk(): + """ + electron-builder's own bundled AppX tooling download (winCodeSign-*.7z) + extracts symlinks for binaries this target never uses and fails without + SeCreateSymbolicLinkPrivilege -- reproduced on this machine's build + shell. ELECTRON_BUILDER_WINDOWS_KITS_PATH (app-builder-lib's + getWindowsKitsBundle) is the documented escape hatch; this must be set + automatically, not left as a step a future build is expected to + remember. + """ + src = BUILD_WIN_MSIX.read_text(encoding="utf-8") + assert "ELECTRON_BUILDER_WINDOWS_KITS_PATH" in src + assert "makeappx.exe" in src + + +# ── main.js needs nothing new for this target ────────────────────────────── +# +# Unlike Light (which needed hasBundledNode/winCanElevateServiceMode/the +# create-group gate because the bundle itself is smaller), MSIX ships +# everything Full does, and the two elevation paths it cannot get from an +# installer already exist independently of installer.nsh: +# firewall.ps1's per-first-use Windows prompt (no admin needed for that +# fallback -- see firewall.ps1's own header) and main.js's +# winElevateServiceMode(), driven from the Node page ("the other door", +# already exercised by test_can_elevate_checks_service_mode_ps1_actually_ +# exists above) rather than from setup. There is deliberately no +# MSIX-specific test here pinning main.js: the Light-target tests above +# already pin that winCanElevateServiceMode() checks for service-mode.ps1's +# presence generically, which is exactly what makes it work for a third +# packaged target without being told about it. diff --git a/packaging/win/build-win-msix.ps1 b/packaging/win/build-win-msix.ps1 new file mode 100644 index 0000000..9f7f667 --- /dev/null +++ b/packaging/win/build-win-msix.ps1 @@ -0,0 +1,134 @@ +<# +.SYNOPSIS + Build the Windows "MSIX" package: same feature set as Full (bundled node + + ffmpeg), packaged for Microsoft Store submission instead of NSIS. + +.DESCRIPTION + See C:\Users\admin\devel\msix-installer.md for the plan this implements. + Unlike Light, this target does NOT drop anything from Full's feature + set -- it exists because Store certification of the NSIS "MSI/EXE" + submission failed for a reason MSIX sidesteps entirely (an unsigned, + internet-downloaded installer never gets a chance to run under + Microsoft's own unattended validation bot; see msix-installer.md §2), + not because the bundled node/service-mode/autostart machinery needed + removing. The one real change is *when* the two elevated operations + (firewall rule, service-mode install) can happen: an AppX/MSIX install + never elevates, so neither can run at install time the way + build/installer.nsh's customInstall macro does. Both already have an + elevation path that does not depend on the installer at all -- + firewall.ps1's own per-first-use Windows prompt, and main.js's + winElevateServiceMode() ("the other door", already used by the Node + page today) -- so nothing in src/main.js needed to change for this + target to work. + + Steps 1-5 are identical to build-win.ps1 (build-win-common.ps1 for 1-4, + build-node-runtime.ps1 for 5 -- this target ships the same frozen daemon + Full does). Step 6 hands electron-builder a standalone config + (electron-builder.msix.yml) via --config, same reasoning as Light: the + two targets' extraResources and signing configuration must never merge. + + Output: packages/meshbay-client/dist-msix/MeshBay-<version>.appx + +.PARAMETER SkipFfmpeg + Passed through to build-node-runtime.ps1 -- skip bundling ffmpeg. Smaller, + streaming-less build for local iteration only. + +.PARAMETER NoElectronBump + Keep the pinned Electron instead of upgrading to the latest release. + +.PARAMETER SkipNodeRuntime + Reuse an existing packages/meshbay-client/node-runtime/ (faster iteration + on the electron-builder side). +#> +[CmdletBinding()] +param( + [switch]$SkipFfmpeg, + [switch]$NoElectronBump, + [switch]$SkipNodeRuntime +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +$WinDir = $PSScriptRoot +$Repo = (Resolve-Path (Join-Path $WinDir "..\..")).Path +$Client = Join-Path $Repo "packages\meshbay-client" +$Config = Join-Path $WinDir "electron-builder.msix.yml" + +. (Join-Path $WinDir "build-win-common.ps1") + +if (-not (Test-Path $Config)) { throw "missing $Config" } + +# electron-builder's AppX target downloads its own bundled Windows Kits tools +# (winCodeSign-*.7z) unless ELECTRON_BUILDER_WINDOWS_KITS_PATH already points +# at a real one. That archive's 7z extraction creates symlinks for binaries +# this target never uses (its macOS/Linux signing tools), and fails outright +# without SeCreateSymbolicLinkPrivilege -- which this machine's build shell +# does not hold. The Windows 10 SDK already installed here has everything +# actually needed (makeappx.exe, signtool.exe); point electron-builder at +# it instead of fighting that extraction. Only set if the caller hasn't +# already pointed it somewhere themselves. +if (-not $env:ELECTRON_BUILDER_WINDOWS_KITS_PATH) { + $kitsRoot = "C:\Program Files (x86)\Windows Kits\10\bin" + $kit = Get-ChildItem $kitsRoot -ErrorAction SilentlyContinue | + Where-Object { $_.PSIsContainer -and $_.Name -match '^\d+\.\d+\.\d+\.\d+$' ` + -and (Test-Path (Join-Path $_.FullName "x64\makeappx.exe")) } | + Sort-Object Name -Descending | Select-Object -First 1 + if (-not $kit) { + throw ("No Windows 10 SDK with makeappx.exe found under $kitsRoot -- " + + "install the Windows 10 SDK (the App Certification Kit / MSIX " + + "Packaging Tools component covers it), or set " + + "ELECTRON_BUILDER_WINDOWS_KITS_PATH yourself.") + } + $env:ELECTRON_BUILDER_WINDOWS_KITS_PATH = Join-Path $kit.FullName "x64" +} +Step "Windows Kits: $env:ELECTRON_BUILDER_WINDOWS_KITS_PATH" + +# --- 1. Node ------------------------------------------------------------- +Assert-NodeVersion + +Push-Location $Client +try { + # --- 2. deps ------------------------------------------------------ + Invoke-NpmCi + + # --- 3. Electron: build against the latest release -------------- + Invoke-ElectronBump -NoElectronBump:$NoElectronBump -WinDir $WinDir + + # --- 4. UI ----------------------------------------------------- + Invoke-SyncUi + + # --- 5. node runtime --------------------------------------- + $rtExe = Join-Path $Client "node-runtime\meshbay-node.exe" + if ($SkipNodeRuntime -and (Test-Path $rtExe)) { + Step "reusing existing node-runtime/" + } else { + Step "building the bundled node (PyInstaller)" + $rtArgs = @{} + if ($SkipFfmpeg) { $rtArgs["SkipFfmpeg"] = $true } + & (Join-Path $WinDir "build-node-runtime.ps1") @rtArgs + if ($LASTEXITCODE -ne 0) { throw "build-node-runtime.ps1 failed" } + } + + # --- 6. package --------------------------------------- + Step "electron-builder --win appx --config electron-builder.msix.yml" + & npx electron-builder --win appx --config $Config + if ($LASTEXITCODE -ne 0) { throw "electron-builder failed" } +} +finally { + Pop-Location +} + +$pkg = Get-ChildItem (Join-Path $Client "dist-msix") -Filter "*.appx" -ErrorAction SilentlyContinue | + Sort-Object LastWriteTime | Select-Object -Last 1 +Write-Host "" +if ($pkg) { + Write-Host "OK package: $($pkg.FullName)" -ForegroundColor Green + Write-Host (" ({0:N0} MB)" -f ($pkg.Length / 1MB)) + Write-Host " unsigned by design -- Microsoft signs it at publish time" -ForegroundColor DarkGray + Write-Host " (msix-installer.md 3)." -ForegroundColor DarkGray +} +else { + Write-Host "!! no *.appx found in $Client\dist-msix" -ForegroundColor Red + exit 1 +} diff --git a/packaging/win/electron-builder.msix.yml b/packaging/win/electron-builder.msix.yml new file mode 100644 index 0000000..4026d46 --- /dev/null +++ b/packaging/win/electron-builder.msix.yml @@ -0,0 +1,113 @@ +# Standalone electron-builder config for the "MSIX" Windows target: same +# feature set as Full (bundled node + ffmpeg), packaged for Microsoft Store +# submission instead of NSIS. See C:\Users\admin\devel\msix-installer.md for +# the plan this implements -- read that first, especially §4 (why the +# installer can carry none of installer.nsh's elevation logic: an AppX/MSIX +# install never elevates, by design) and §8 (what is still unverified here). +# +# Deliberately NOT layered onto package.json's `build` field, same reasoning +# as electron-builder.light.yml: --config reads ONLY this file, so nothing +# here can accidentally inherit or merge with Full's `nsis`/signing config. +# +# electron-builder's target key for this is `appx`, not `msix` -- this +# version (app-builder-lib's AppxTarget.js, checked against the installed +# ^26.15.3) has no separate `msix` target. It still produces a package +# Partner Center's MSIX upload flow accepts; the mismatch is a naming +# artifact of the tool, not a statement about which container format comes +# out. Re-check this against whatever version is installed if it is ever +# bumped -- do not assume the target name from memory. +# +# No CSC (code-signing cert) is configured for this target, and that is +# correct, not an oversight: per app-builder-lib's own +# windowsSignToolManager.js (computePublisherName), an AppX target built +# with no certificate configured is logged as "Windows Store only build" and +# left unsigned, with `publisher` written into the manifest as-is. Microsoft +# signs the package itself at publish time (msix-installer.md §3) -- signing +# it here first would be pointless work, not extra safety. + +appId: org.meshbay.client +productName: MeshBay + +directories: + # Full's own build lives in dist/, Light's in dist-light/ -- a third, + # separate output dir so no two targets ever race on or clobber each + # other's files. + output: dist-msix + +files: + - src/** + - ui/** + +win: + target: appx + icon: build/icon.ico + artifactName: "MeshBay-${version}.${ext}" + extraResources: + # Same bundle as Full (package.json's own build.win.extraResources) -- + # this target drops NSIS, not the node/ffmpeg runtime or the two service + # scripts. service-mode.ps1 in particular still works unmodified: it is + # invoked from main.js's winElevateServiceMode() on demand, from the + # running (unelevated) app, not from an installer step -- that path + # already existed before this target did (see "the other door" comment + # in src/main.js) and needs nothing new here beyond the file being + # present to find. + - from: node-runtime + to: node-runtime + filter: + - "**/*" + - from: ../../packaging/win/firewall.ps1 + to: firewall.ps1 + - from: ../../packaging/win/service.ps1 + to: service.ps1 + - from: ../../packaging/win/service-mode.ps1 + to: service-mode.ps1 + +appx: + # --- Real values, from Partner Center's "App identity" page (App + # management -> App identity), not chosen freely -- a mismatch here fails + # Store validation outright rather than warning. publisherDisplayName is + # "MeshBay" as Partner Center assigned it, NOT package.json's own author + # company name ("MeshBay Team") -- AppXOptions.d.ts's default (company + # name from app metadata) would silently pick the wrong one if this were + # left unset, so it must stay explicit even though it looks redundant + # next to `displayName`. + identityName: MeshBay.MeshBay + publisher: "CN=CE32BB0D-6B7C-4D3A-AA42-E259B778CAC9" + publisherDisplayName: MeshBay + applicationId: MeshBay + displayName: MeshBay + languages: + - en-US + - fr-FR + - es-ES + - pt-BR + - zh-CN + - ja-JP + - de-DE + - it-IT + - nl-NL + - pl-PL + # Both are the "common" (general, non-restricted) capability group per + # app-builder-lib's AppxCapabilities.js -- no special Store justification + # needed, unlike the `rescap`-namespaced restricted ones. Matches + # firewall.ps1's own rules, which are `-Profile Any` (private AND public + # network). Whether Windows Firewall actually auto-exempts a full-trust + # packaged app on the strength of these declarations -- eliminating the + # elevation firewall.ps1 exists for entirely -- is msix-installer.md §8's + # #1 open item: verify live before relying on it, the declaration alone + # only proves the manifest is well-formed. + capabilities: + - internetClientServer + - privateNetworkClientServer + # windows.startupTask, the MSIX-native equivalent of the NSIS "at sign in" + # mode's Startup-folder .vbs (main.js's WIN_STARTUP_VBS) -- but pointed at + # the node daemon, not the Electron shell, which is what `addAutoLaunchExtension: + # true` would do instead (it always targets the package's own main + # executable, per AppxTarget.js's `executable` macro -- there is no config + # switch to point it at a different bundled exe). build/appx-extensions.xml + # declares that extension by hand for exactly this reason. Whether + # Windows actually launches a *non-primary* bundled exe through this + # mechanism is the other open item in msix-installer.md §8 -- untested + # until sideloaded. + customExtensionsPath: build/appx-extensions.xml + showNameOnTiles: false |