Skip to content

fix(updater): never open a raw .app.zip from the Download button (auto-update S3)#23

Merged
ndemianc merged 1 commit into
developfrom
fix/updater-download-url
Jul 20, 2026
Merged

fix(updater): never open a raw .app.zip from the Download button (auto-update S3)#23
ndemianc merged 1 commit into
developfrom
fix/updater-download-url

Conversation

@ndemianc

Copy link
Copy Markdown
Contributor

What

The notify-only updater's Download action was feed.url || product.downloadUrl || base. That was right while feed.url was a release page — but S2 (thin.ly #371) made it the arch-matched LevelCode-<arch>.app.zip, an artifact for Squirrel to install, not a page to open. Left alone, the button starts a raw zip download.

Now a pure helper, U.downloadUrl(feed, product, base):

product.downloadUrl  →  feed.releaseNotesUrl  →  base

feed.url is dropped from the human path entirely, so no future feed change can put a zip behind that button.

Why now, and not with S4

This gated the next release, not the flag flip. The notify-only client is served a 200 regardless of LEVELCODE_UPDATE_FEED_SIGNED:

return head(:no_content) unless notify_only_client? || (signed_feed? && rel[:installable])

So it would have broken on the first release carrying an S1 zip, with the flag still off. That is a different constraint from the documented "don't flip the flag early" one — and unlike that one, nothing in the code would have caught it (the controller enforces flag-ordering via installable; there is no equivalent guard on the notify path). docs/AUTO-UPDATE.md recorded S3 as merely preceding S4; it now records both constraints and marks S1–S3 done.

Verification

  • 9 unit tests pass, 2 new.
  • Mutation-tested: restoring the old preference order fails the new test with
    actual: …/LevelCode-arm64.app.zip vs expected: https://levelcode.ai/download.
  • node --check clean on both files.

Next

S4 — flip LEVELCODE_UPDATE_FEED_SIGNED=1, then the real install-N → publish-N+1 test. Note S4 is inert until a release actually carries the S1 zips: everything published so far resolves to installable: false, so Squirrel still gets 204.

🤖 Generated with Claude Code

…o-update S3)

The notify-only updater's Download action was `feed.url || product.downloadUrl ||
base`. That was correct while `feed.url` was a release page — but S2 made it the
arch-matched LevelCode-<arch>.app.zip, an artifact for Squirrel to INSTALL. Left
alone, the button would start a raw zip download instead of showing a page.

Extracted the choice into a pure `U.downloadUrl(feed, product, base)`:
`product.downloadUrl` → `feed.releaseNotesUrl` → base. `feed.url` is dropped from
the human path entirely, so no future feed change can put a zip behind it.

Sequencing correction (the reason this is worth shipping now, not with S4): the
notify-only client is served a 200 REGARDLESS of LEVELCODE_UPDATE_FEED_SIGNED —
`unless notify_only_client? || (signed_feed? && rel[:installable])`. So this broke
on the next release carrying an S1 zip, not on flipping the flag. Nothing in the
code would have caught that, unlike the flag-ordering constraint which the
controller now enforces via `installable`. AUTO-UPDATE.md said S3 merely preceded
S4; it now records both constraints and marks S1-S3 done.

Verified: 9 unit tests pass (2 new), and restoring the old preference order fails
the new test with actual=…/LevelCode-arm64.app.zip vs expected=levelcode.ai/download.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 20, 2026 02:47

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

Updates the notify-only updater extension so the Download action never opens a raw LevelCode-<arch>.app.zip (now used for Squirrel install artifacts), and instead always routes humans to a page-style URL.

Changes:

  • Added U.downloadUrl(feed, product, base) helper to explicitly prefer human-facing URLs (product.downloadUrlfeed.releaseNotesUrlbase) and exclude feed.url.
  • Updated the extension’s Download action to use the helper rather than feed.url.
  • Added regression/unit tests and updated docs/AUTO-UPDATE.md to capture the sequencing risk and completed slices.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
extensions/levelcode-updater/update.js Introduces downloadUrl() helper that avoids feed.url and encodes the intended precedence for human-facing links.
extensions/levelcode-updater/extension.js Switches the “Download” notification action to call U.downloadUrl(...).
extensions/levelcode-updater/test/update.test.js Adds regression tests ensuring downloadUrl() never returns feed.url and degrades safely with null inputs.
docs/AUTO-UPDATE.md Documents the new behavior, marks S1–S3 done, and clarifies sequencing constraints/risk.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ndemianc
ndemianc merged commit 876afa9 into develop Jul 20, 2026
2 checks passed
@ndemianc
ndemianc deleted the fix/updater-download-url branch July 20, 2026 02:53
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.

2 participants