feat: recognize .msix / .msixbundle assets and 'msix' package format#26
feat: recognize .msix / .msixbundle assets and 'msix' package format#26jjeff wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds MSIX/MSIXBUNDLE support across file extension, package format, OS/architecture detection, and update feed behavior to better support Windows MSIX auto-updaters (Squirrel.Mac-shaped JSON).
Changes:
- Add
.msixand.msixbundleto supported extensions and Windows download extension resolution whenpkg=msix. - Add
msixas a recognizedPackageFormatand detect MSIX/MSIXBUNDLE filenames accordingly. - Extend OS/architecture detection to treat
.msixbundleas universal and.msix/.msixbundleas Windows; add/update unit tests and Pecans update route tests.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/unit/pecans.spec.ts | Adds tests asserting update JSON shape and URL behavior for MSIX/MSIXBUNDLE requests. |
| test/unit/SupportedFileExtension.spec.ts | Extends extension support tests and Windows download extension selection for pkg=msix. |
| test/unit/PackageFormat.spec.ts | Adds msix to formats and tests filename/query detection. |
| test/unit/OperatingSystem.spec.ts | Adds Windows detection tests for .msix and .msixbundle. |
| test/unit/Architecture.spec.ts | Adds architecture detection tests for .msix vs .msixbundle (universal). |
| src/utils/SupportedFileExtension.ts | Adds MSIX extensions and updates Windows download extension resolution for pkg=msix. |
| src/utils/PackageFormat.ts | Adds msix to PACKAGE_FORMATS and detects .msix/.msixbundle filenames. |
| src/utils/OperatingSystem.ts | Recognizes .msix and .msixbundle as Windows. |
| src/utils/Architecture.ts | Treats .msixbundle as universal architecture. |
- SupportedFileExtension.ts: revert the misleading comment claim about special-casing .msixbundle. path.extname() already returns ".msixbundle" correctly for files with that extension; only .tar.gz needs the explicit endsWith() check because extname returns ".gz" for files ending in ".tar.gz". - pecans.spec.ts: add channel:"stable" to the msixbundle test for parameter-shape consistency with the preceding msix test, so both cases exercise the same route contract.
Electron 41+'s autoUpdater has native MSIX support and consumes the
existing Squirrel.Mac-shaped JSON response format from /update/:platform/:version.
For Pecans to act as the update server for an MSIX-packaged Electron app,
it only needs to recognize the new asset extensions and surface them as
download candidates when callers request the 'msix' package format.
- Add .msix and .msixbundle to SUPPORTED_FILE_EXTENSIONS.
- Add 'msix' to PACKAGE_FORMATS; filenameToPackageFormat detects both
.msix and .msixbundle.
- filenameToOperatingSystem maps .msix / .msixbundle to 'windows'.
- filenameToArchitecture treats .msixbundle as 'universal' (multi-arch
bundle); single-arch .msix continues to derive arch from filename.
- getDownloadExtensionsByOs('windows', 'msix') returns
['.msixbundle', '.msix'], preferring the bundle when both are present.
The existing /update/:platform/:version JSON endpoint already emits the
{url, name, notes, pub_date} shape Electron's MSIX updater expects; with
the recognition above, callers pass ?filetype=msix (or msixbundle) and
the resolved download URL routes through /download/version/.../?filetype=msix
to the appropriate asset.
Tests cover extension/package/OS/arch detection and the update endpoint's
behavior for Windows MSIX clients.
- SupportedFileExtension.ts: revert the misleading comment claim about special-casing .msixbundle. path.extname() already returns ".msixbundle" correctly for files with that extension; only .tar.gz needs the explicit endsWith() check because extname returns ".gz" for files ending in ".tar.gz". - pecans.spec.ts: add channel:"stable" to the msixbundle test for parameter-shape consistency with the preceding msix test, so both cases exercise the same route contract.
…gestion filenameToPlatform built keys like WINDOWS_MSIX_UNIVERSAL that don't exist in the platforms record, so the GitHub backend dropped every .msix and .msixbundle asset and ?filetype=msix silently resolved to the .exe. - add windows_universal to PLATFORMS; .msixbundle assets now type as windows_universal, single-arch .msix as windows_32/64 - exclude msix from platform id composition (windows ids don't encode pkg; msix assets resolve by filetype instead) - resolveReleaseAssetForVersion matches windows_universal for arch-specific windows requests, but only when an msix filetype is explicitly wanted so default flows keep serving arch-specific .exe installers - versions.filter counts windows_universal assets toward availability for arch-specific windows queries so bundle-only releases don't 404 - cover the ingestion seam (normalizeRelease/filenameToPlatform) and resolution behavior with tests Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018YjpR5uuatwzQvNbpDo8n4
dopry
left a comment
There was a problem hiding this comment.
Thanks for this — the direction is right, the utility-layer changes are clean, and the added unit tests are well written. Unfortunately there's an integration gap that keeps the feature from working end-to-end, found by executing this branch rather than just reading it:
filenameToPlatform returns undefined for every MSIX asset, because the composed key (WINDOWS_MSIX_64 / WINDOWS_MSIX_UNIVERSAL) doesn't exist in the platforms record. The GitHub backend drops any asset whose platform resolves to null/undefined, so no .msix/.msixbundle ever becomes a release asset. Net effect: ?filetype=msix silently serves the .exe when one exists (an MSIX updater can't apply that), or 404s on an MSIX-only release. Details in the inline comments, along with a second issue: windows_universal assets aren't reachable from windows_64 requests in resolveReleaseAssetForVersion/versions.filter even after the platform map is fixed.
The existing 637 tests do pass (confirmed locally), but the new tests all stop short of the ingestion→resolution seam where this lives — suggestions for seam tests are inline.
A ready-made fix is available: branch claude/pecans-pr-26-review-249u6k on this repo contains your two commits plus a fix commit addressing all of the above, with tests at the seam (655 unit tests pass, tsc clean). It's a fast-forward on top of your head, so you can pull it straight into this PR:
git checkout feat/msix-support
git pull https://github.com/dopry/pecans claude/pecans-pr-26-review-249u6k
git push
On downstream compatibility (important for existing pecans consumers): with the fix applied, default windows downloads still resolve [".exe"] unless pkg=msix/filetype=msix is explicitly requested, the Squirrel.Windows RELEASES/.nupkg path is untouched, and there's a regression test pinning that the .exe still wins default resolution when MSIX assets are published alongside it. The PackageFormat/SupportedFileExtension unions widen, which only affects TypeScript consumers doing exhaustive switches — semver-minor.
Generated by Claude Code
| const name = filename.toLowerCase(); | ||
| if (name.endsWith(".deb")) return "deb"; | ||
| if (name.endsWith(".rpm")) return "rpm"; | ||
| if (name.endsWith(".msix") || name.endsWith(".msixbundle")) return "msix"; |
There was a problem hiding this comment.
Blocker: returning "msix" here breaks filenameToPlatform (src/utils/platforms.ts), which composes the platform key as os_pkg_arch — for MSIX assets that produces WINDOWS_MSIX_64 / WINDOWS_MSIX_UNIVERSAL, neither of which exists in the platforms record, so it returns undefined.
The GitHub backend filters release assets with filenameToPlatform(asset.name) != null (src/backends/github.ts:229-236), so every .msix/.msixbundle asset is silently dropped at ingestion and the feature doesn't work end-to-end. Verified by running the code on this branch:
filenameToPlatform("Visibox-5.0.13.msixbundle") → undefined
filenameToPlatform("Visibox_5.0.13.0_x64.msix") → undefined
Worse, because resolveReleaseAssetForVersion keeps all supported extensions as fallbacks, ?filetype=msix then silently serves the .exe when one exists — an MSIX updater receives an installer it can't apply — or 404s when the release only has MSIX assets.
Fix: keep msix out of the platform-id composition in filenameToPlatform (windows platform ids don't encode pkg; msix resolves by filetype) and add windows_universal to PLATFORMS so .msixbundle gets a valid type.
Generated by Claude Code
| const name = filename.toLowerCase(); | ||
| if (name == "releases") return "universal"; | ||
| // .msixbundle is a multi-architecture bundle by definition | ||
| if (name.endsWith(".msixbundle")) return "universal"; |
There was a problem hiding this comment.
Mapping .msixbundle → universal is right in principle, but nothing downstream can match it for windows clients. resolveReleaseAssetForVersion filters with asset.type.startsWith(platform) and only adds a universal fallback for OSX (src/utils/resolveForVersion.ts:26-28); versions.ts:53-56 has the same osx-only special case. So a client on windows_64 — the platform your own handleUpdateOSX tests use, and what the win32-x64 legacy mapping produces — would never resolve a bundle even once the platform-map issue is fixed.
Two additions are needed:
resolveReleaseAssetForVersion: also matchwindows_universalfor arch-specific windows requests — but gate it on the wanted filetype being.msix/.msixbundle, otherwise a published bundle would start outranking the.exefor existing Squirrel clients (the type-length sort prefers longer types).versions.filter: countwindows_universalassets toward availability forwindows_*queries, so a bundle-only release doesn't 404 the update endpoint.
Related edge worth a comment or test: arm64-only .msix files (App_arm64.msix) resolve to arch arm64, which isn't a supported windows arch (getSupportedArchByOs), so they're dropped — arm64 MSIX clients need the .msixbundle path.
Generated by Claude Code
| ); | ||
| }); | ||
|
|
||
| it("should produce Squirrel.Mac-shaped JSON for Windows MSIX clients", async () => { |
There was a problem hiding this comment.
These two tests mock pecans.versions.filter and assert only the shape of the JSON/URL string, so they pass even though MSIX assets never survive backend ingestion — the bug lives in the seam between normalizeRelease/filenameToPlatform and resolveReleaseAssetForVersion, which nothing here crosses.
Suggest adding:
- a
normalizeReleasetest (ingithub.spec.ts) asserting a release with.exe+.msix+.msixbundleassets keeps all three, with the expectedtype/arch/pkg; resolveReleaseAssetForVersiontests:filetype=msixpicks the.msix/bundle over the.exe, and — the downstream-compat guarantee — a default request (no filetype) still picks the.exewhen msix assets are published alongside it.
Generated by Claude Code
|
Closing as superseded by #46. Since this branch was opened, #46 reimplements the feature on the new architecture with the same external contract ( Generated by Claude Code |
Summary
Adds MSIX support to Pecans so it can act as the update server for an Electron 41+ app packaged as MSIX. Electron 41's
autoUpdaterconsumes the existing Squirrel.Mac-shaped JSON the/update/:platform/:versionendpoint already emits — Pecans only needs to recognize.msix/.msixbundleas Windows assets and route to them when callers request themsixpackage format.Changes
SUPPORTED_FILE_EXTENSIONSwith.msixand.msixbundle.PACKAGE_FORMATSwithmsix;filenameToPackageFormatdetects both extensions.filenameToOperatingSystemmaps.msix/.msixbundle→windows.filenameToArchitecturetreats.msixbundleasuniversal(it's a multi-arch bundle by definition); single-arch.msixcontinues to derive arch from filename suffix (_x64,_arm64,_x86).getDownloadExtensionsByOs('windows', 'msix')returns['.msixbundle', '.msix'], preferring the bundle when both are published.The existing
/update/:platform/:versionJSON endpoint already emits the{url, name, notes, pub_date}shape Electron's MSIX updater consumes. Callers (Electron'sautoUpdater.setFeedURL) just pass?filetype=msixor?filetype=msixbundleand the resolved download URL routes through/download/version/.../?filetype=msixto the matching asset.Test plan
handleUpdateOSXproduces a Squirrel.Mac-shaped JSON response with a.msix/.msixbundleURL when called with the appropriate filetypetsc --noEmitcleanNotes
This is the server-side half of MSIX support for spaceagetv/missioncontrol (Visibox). Reference: Electron 41 release notes and electron/electron#49230.
🤖 Generated with Claude Code