Skip to content

feat: recognize .msix / .msixbundle assets and 'msix' package format - #26

Closed
jjeff wants to merge 3 commits into
dopry:nextfrom
jjeff:feat/msix-support
Closed

feat: recognize .msix / .msixbundle assets and 'msix' package format#26
jjeff wants to merge 3 commits into
dopry:nextfrom
jjeff:feat/msix-support

Conversation

@jjeff

@jjeff jjeff commented May 22, 2026

Copy link
Copy Markdown
Contributor

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 autoUpdater consumes the existing Squirrel.Mac-shaped JSON the /update/:platform/:version endpoint already emits — Pecans only needs to recognize .msix / .msixbundle as Windows assets and route to them when callers request the msix package format.

Changes

  • Extend SUPPORTED_FILE_EXTENSIONS with .msix and .msixbundle.
  • Extend PACKAGE_FORMATS with msix; filenameToPackageFormat detects both extensions.
  • filenameToOperatingSystem maps .msix / .msixbundlewindows.
  • filenameToArchitecture treats .msixbundle as universal (it's a multi-arch bundle by definition); single-arch .msix continues 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/:version JSON endpoint already emits the {url, name, notes, pub_date} shape Electron's MSIX updater consumes. Callers (Electron's autoUpdater.setFeedURL) just pass ?filetype=msix or ?filetype=msixbundle and the resolved download URL routes through /download/version/.../?filetype=msix to the matching asset.

Test plan

  • Existing 637 tests still pass
  • +2 new unit tests verify handleUpdateOSX produces a Squirrel.Mac-shaped JSON response with a .msix / .msixbundle URL when called with the appropriate filetype
  • Extension / package format / OS / architecture detection covered by unit tests
  • tsc --noEmit clean

Notes

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

@jjeff
jjeff marked this pull request as ready for review May 23, 2026 03:02
Copilot AI review requested due to automatic review settings May 23, 2026 03:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 .msix and .msixbundle to supported extensions and Windows download extension resolution when pkg=msix.
  • Add msix as a recognized PackageFormat and detect MSIX/MSIXBUNDLE filenames accordingly.
  • Extend OS/architecture detection to treat .msixbundle as universal and .msix/.msixbundle as 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.

Comment thread src/utils/SupportedFileExtension.ts Outdated
Comment thread test/unit/pecans.spec.ts Outdated
jjeff added a commit to jjeff/pecans that referenced this pull request May 23, 2026
- 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.
jjeff added 2 commits June 26, 2026 15:04
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.
@dopry
dopry force-pushed the feat/msix-support branch from 615329f to 5dd07de Compare June 26, 2026 19:04
…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 dopry left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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";

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/utils/Architecture.ts
const name = filename.toLowerCase();
if (name == "releases") return "universal";
// .msixbundle is a multi-architecture bundle by definition
if (name.endsWith(".msixbundle")) return "universal";

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mapping .msixbundleuniversal 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 match windows_universal for arch-specific windows requests — but gate it on the wanted filetype being .msix/.msixbundle, otherwise a published bundle would start outranking the .exe for existing Squirrel clients (the type-length sort prefers longer types).
  • versions.filter: count windows_universal assets toward availability for windows_* 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

Comment thread test/unit/pecans.spec.ts
);
});

it("should produce Squirrel.Mac-shaped JSON for Windows MSIX clients", async () => {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 normalizeRelease test (in github.spec.ts) asserting a release with .exe + .msix + .msixbundle assets keeps all three, with the expected type/arch/pkg;
  • resolveReleaseAssetForVersion tests: filetype=msix picks the .msix/bundle over the .exe, and — the downstream-compat guarantee — a default request (no filetype) still picks the .exe when msix assets are published alongside it.

Generated by Claude Code

dopry commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Closing as superseded by #46.

Since this branch was opened, next went through the Phase 5–7 refactors (#41#45): versions.ts and resolveForVersion.ts — where half of this diff lives — were replaced by the unified ReleaseService pipeline on the discrete {os, arch, pkg} model, so a rebase couldn't be resolved mechanically and this fork branch isn't writable from the maintainer side to update the PR in place.

#46 reimplements the feature on the new architecture with the same external contract (?filetype=msix|msixbundle on the update/download routes, bundle preferred over single-arch .msix, default windows flows still resolving the .exe), ports all of this PR's tests, and adds end-to-end coverage through the HTTP surface. @jjeff's authorship is credited via Co-Authored-By on the commit — thank you for the contribution!


Generated by Claude Code

@dopry dopry closed this Jul 16, 2026
dopry added a commit that referenced this pull request Jul 21, 2026
…46)

* feat: recognize .msix / .msixbundle assets and 'msix' package format

Reimplementation of #26 on the unified ReleaseService resolution
pipeline (msix as a first-class package format on the discrete
{os, arch, pkg} model):

- SUPPORTED_FILE_EXTENSIONS gains .msix / .msixbundle;
  getDownloadExtensionsByOs("windows", "msix") returns
  [".msixbundle", ".msix"], preferring the multi-arch bundle.
- PACKAGE_FORMATS gains "msix"; filenameToPackageFormat detects both
  extensions; new filetypeToPackageFormat maps ?filetype=msix|msixbundle
  to the msix pkg at the HTTP edge (other filetypes keep their
  platform-default semantics).
- filenameToOperatingSystem maps .msix / .msixbundle to windows;
  filenameToArchitecture treats .msixbundle as universal (multi-arch by
  definition), single-arch .msix derives arch from the filename suffix.
- New composite platform ids windows_msix{,_32,_64,_universal} so msix
  assets survive ingestion; arm64 .msix drops like any arm64 windows
  asset.
- assetMatchesPlatform: a universal msix asset satisfies any windows
  arch when msix is explicitly requested; default windows flows
  (pkg "default") never widen to msix, so Squirrel clients keep
  resolving the .exe when msix assets are published alongside it.
- /update/:platform/:version?filetype=msix serves the existing
  Squirrel.Mac-shaped JSON that Electron 41+'s MSIX autoUpdater
  consumes; /download/...?filetype=msix and /dl/windows/:arch?pkg=msix
  route to the assets. The Squirrel.Windows RELEASES/.nupkg path is
  untouched.

Co-Authored-By: Jeff Robbins <321284+jjeff@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019F5tixJXBm2R1FAjAfH1se

* style: prettier formatting in msix integration spec

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019F5tixJXBm2R1FAjAfH1se

* fix: canonicalize filetype casing in the update feed url

filetypeToPackageFormat accepts ?filetype=MSIX case-insensitively, but
the handler embedded the caller's casing into the generated download
url, which the download route's case-sensitive filetype validation then
rejects with a 400. Lowercase the filetype before both the pkg mapping
and the feed url.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019F5tixJXBm2R1FAjAfH1se

* fix: don't let ?filetype constrain exact-filename downloads

The filetype->pkg implication belongs to platform-based resolution; an
explicit :filename already identifies the asset and its package format,
so a stray ?filetype=msix on an .exe filename request must not narrow
release resolution (previously a 404 when no msix assets exist). Also
reword the filetypeToPackageFormat doc comment: internal windows_msix*
composite ids exist for ingestion, they're just not part of the legacy
request vocabulary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019F5tixJXBm2R1FAjAfH1se

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jeff Robbins <321284+jjeff@users.noreply.github.com>
github-actions Bot pushed a commit that referenced this pull request Jul 21, 2026
# [2.0.0-next.18](v2.0.0-next.17...v2.0.0-next.18) (2026-07-21)

* feat!: unified ReleaseService resolution pipeline on discrete os/arch/pkg (Phase 7 PR B) ([#44](#44)) ([8a2f06a](8a2f06a))

### Bug Fixes

* **github:** default octokit to native fetch to prevent empty release lists ([#28](#28)) ([0f6eac6](0f6eac6))
* honor route params in downloads, dead code removal, small fixes (Phase 2) ([#31](#31)) ([9fd8f79](9fd8f79))
* semantic-release trusted publishing ([4f1ee88](4f1ee88))
* semantic-release trusted publishing ([#53](#53)) ([3d4d4ce](3d4d4ce))
* working generic refresh webhook; document raw as the backend-private asset slot (Phase 7 PR A) ([#43](#43)) ([23d1d9f](23d1d9f))

### chore

* esm only ([#24](#24)) ([1b8a546](1b8a546))

### Features

* dependency modernization — Express 5, octokit 22, remove UA autodetection (Phase 5) ([#41](#41)) ([8ea329a](8ea329a))
* modernize packaging and dev tooling (Phase 3) ([#32](#32)) ([3345ed7](3345ed7))
* recognize .msix / .msixbundle assets and 'msix' package format ([#46](#46)) ([4f848b9](4f848b9)), closes [#26](#26)
* typed HTTP errors with router-scoped error handling (Phase 6) ([#42](#42)) ([e3b7b54](e3b7b54))

### BREAKING CHANGES

* @dopry/pecans is now ESM-only. require('@dopry/pecans')
is no longer supported; use import (Node >= 22.12).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zXcPSv6FFTPgD59T4KudM

* refactor: import model types with import type in runtime modules

Follows up on Copilot review: PecansRelease/PecansReleases (and other
names used only in type positions) are classes, so tsc accepts plain
imports, but with verbatimModuleSyntax they would stay in the emitted
JS as runtime imports. Convert the type-only usages to import type to
keep the runtime module graph minimal and cycle-free.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zXcPSv6FFTPgD59T4KudM
* Pecans no longer exposes a versions property.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zUyj4PpSog9RkrYxzFRhM

* feat!: remove the Versions and resolveReleaseAssetForVersion adapters

The deprecation shims this PR introduced are dropped instead of carried
to 3.0: route handlers and consumers resolve through ReleaseService /
resolveAssetForRelease directly. The table-driven specs that pinned the
legacy composite-id resolution semantics are migrated onto the pipeline
(via platformToQuery) so the behavioral pins survive the adapter
removal; unique Versions coverage moved into service.spec.

Pre-existing deprecations (GitHubBackend, PecansSettings.timeout,
PecansReleaseDTO.channel) keep their 3.0 schedule.
* Versions, VersionFilterOpts, PlatformQuery, and
resolveReleaseAssetForVersion are no longer exported.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zUyj4PpSog9RkrYxzFRhM
* getArchFromUserAgent, getOsFromUserAgent, and
getPlatformFromUserAgent now take the internal UserAgentDetails type
instead of express-useragent's Details, and getArchFromUserAgent
defaults Windows and Linux to '64' (32-bit desktops are effectively
extinct; the function is not used internally).

pecans consumed exactly four booleans from the unmaintained
express-useragent package; src/utils/userAgent.ts derives them from the
User-Agent header directly, with mobile exclusions the old library
handled via separate flags (iOS UAs contain 'like Mac OS X', Android
UAs contain 'Linux'). The middleware attaches the same req.useragent
shape. Unit specs cover the parser; the Phase 1 UA-driven download
contract tests pass unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zUyj4PpSog9RkrYxzFRhM

* fix: validate update-route params through getStringParam consistently

Review feedback: handleUpdateOSX truthiness-checked req.params directly
but read values through getStringParam, and handleUpdateWin had no
version guard at all; a missing tag would have produced a '>=undefined'
range. Validate once through the helper and reuse the validated values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zUyj4PpSog9RkrYxzFRhM

* fix: exclude Macintosh+Mobile webview UAs from macOS detection

Review feedback (partial): a Mobile token alongside Macintosh indicates
an iPad-class webview masquerading as a Mac; genuine macOS browsers
never send it. Fixture + test added. Note true iPadOS desktop-mode UAs
are byte-identical to Mac Safari and undetectable by any parser.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zUyj4PpSog9RkrYxzFRhM

* fix: short-circuit dlfilename when the filename param is absent

Review feedback: an undefined filename passed into queryReleases matches
every release (the predicate treats undefined as no-filter), which would
serve an arbitrary asset instead of a 404. Unreachable via the current
route but guarded for consistency with the update handlers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zUyj4PpSog9RkrYxzFRhM

* feat: remove user-agent platform autodetection
* selecting a platform is now the client's
responsibility. GET / is no longer a download route, and the platform
segment is required on /download, /download/version/:tag, and
/download/channel/:channel (a missing platform returns 400). The
user-agent parser, its middleware, and the getPlatformFromUserAgent /
getArchFromUserAgent / getOsFromUserAgent helpers are removed.

Autodetection only ever served bare browser links - Squirrel update
clients and /dl/* always send explicit platforms - and reliable device
detection is better handled client-side where UA Client Hints are
available. Reverting this commit restores the feature wholesale if
anyone misses it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zUyj4PpSog9RkrYxzFRhM

* fix: remove imports orphaned by the autodetection removal

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zUyj4PpSog9RkrYxzFRhM

* fix: validate tag ranges early in validateReqQueryTag

Review feedback: validRange's result was discarded, so invalid tags only
failed deep in release matching with a generic 'Invalid Range Specified'
error. Invalid ranges now throw UnsupportedTagError at the parameter
boundary ('latest' stays allowed), and the error message no longer says
'channel' for tags (copy-paste from UnsupportedChannelError).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zUyj4PpSog9RkrYxzFRhM
* build output moves from dist/cjs + dist/mjs to a tsup
bundle (dist/index.js CJS, dist/index.mjs ESM). Deep imports into dist
paths no longer resolve; all models (PecansRelease, PecansAsset,
PecansReleases, ...) are now exported from the package root instead.

- replace the dual-tsc + fixup.sh build with tsup (CJS + ESM + d.ts +
  sourcemaps, node22 target); consolidate four tsconfigs into one
  typecheck-only tsconfig.json
- add a proper exports map with types for both module systems; verified
  with publint and arethetypeswrong (all green: node10/node16/bundler)
- declare debug and qs as real dependencies - both are imported directly
  but were only present transitively via express, which broke the ESM
  bundle (inlined CJS require calls)
- guard the run-directly check with typeof require so the ESM build is
  importable; node dist/index.js still starts the server
- ts-node out of runtime dependencies; dev now runs tsx watch; start
  runs the compiled dist; drop nodemon
- ESLint 9 flat config + prettier (replaces the stale mocha-era
  .eslintrc); fix the 19 findings it surfaced (unused imports/vars,
  case-block declarations, error causes, no-useless-assignment)
- add explicit @types/node; add lint/format/typecheck scripts
- package.json: type commonjs, sideEffects false, canonical repo url

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zUyj4PpSog9RkrYxzFRhM

* fix: rank .tar.gz assets by their full extension in resolveForVersion

path.extname reports '.gz' for .tar.gz filenames, so the sort fallback
ranked them at prefs.indexOf(-1) - ahead of every genuine preference -
whenever .tgz and .tar.gz assets coexisted. Use getSupportedExt, which
handles the double extension, matching the Phase 2 fix to
PecansAsset.satisfiesExtensions. Regression test added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zUyj4PpSog9RkrYxzFRhM

* fix: throw Error from configure(), correct Listening typo

Review feedback: the default switch case in configure() threw a raw
string (no stack trace); the startup log said 'Lisening'. The test that
pinned the typo is updated to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zUyj4PpSog9RkrYxzFRhM

* fix: throw on invalid os in getDownloadExtensionsByOs

Review feedback: the switch had no default, so an invalid OperatingSystem
cast in at runtime silently returned undefined against the declared
SupportedFileExtension[] return type. Fail loudly instead; edge-case test
updated to pin the throw.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zUyj4PpSog9RkrYxzFRhM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants