Skip to content

Dpx 693 pr 2 teach lstk update to handle the bundled extensions - #482

Open
carillan81 wants to merge 3 commits into
mainfrom
dpx-693-pr-2-teach-lstk-update-to-handle-the-bundle
Open

Dpx 693 pr 2 teach lstk update to handle the bundled extensions#482
carillan81 wants to merge 3 commits into
mainfrom
dpx-693-pr-2-teach-lstk-update-to-handle-the-bundle

Conversation

@carillan81

Copy link
Copy Markdown
Contributor

Motivation

Once releases ship the bundled extensions (bundled-extensions and lstk-extensions.toml next to lstk), lstk update on the binary channel has to replace all three files together, not just the binary. Today it replaces one file, lstk, and ignores everything else in the archive.

That has a second-order problem. The first bundling release is installed by the updater users already have, which only replaces lstk. They land on a current binary with no extensions, and because they are already on the newest version, lstk update says "already up to date" until the next release ships. Nobody can fix that retroactively, so the new updater has to detect and repair it.

Solution

Set-wise replacement (stage-then-commit). Every member of the archive is copied into the install directory under a .lstk-new name; only when all copies succeed is each renamed over its final name, lstk last. A file visible under its real name is never half-written, a failure before the last rename leaves a working lstk to re-run with, and the updater never deletes an lstk-* file it cannot prove it owns (additive-only). An archive containing only lstk behaves exactly as before, so pre-bundling releases and rollbacks are unaffected.

Repair when current but incomplete. The release stamps the expected set into the binary (version.bundledSet, via ldflags). When versions match but a stamped member is missing or unusable, lstk update reinstalls the same release, then re-checks; if the archive still did not deliver the members it fails loudly ("did not restore the bundled extensions") instead of looping. Empty stamp keeps today's pure version comparison. Homebrew and npm never repair; the package manager owns the whole set there. The passive update notice also nudges an incomplete install toward lstk update.

Hardening. Windows moves every existing member aside before renaming (a running extension no longer breaks the commit); a squatting symlink or directory at a staging path is refused rather than written through; the install directory is listed literally instead of globbed (a [ in the path used to fail every update); setuid/setgid bits survive the update; a concurrent update cannot truncate another's staging file.

Structured output. UpdateCheckedEvent gains RepairBundled; the --json check shape gains "repairBundled": true for a same-version repair (absent otherwise, and scrubbed from the applied shape).

Dependencies and follow-ups

  • dpx-692 (PR 1) must merge first or together: it carries the resolver that dispatches bundled commands from the toml. Without it a repaired install has the files but nothing resolves them.
  • The bundledSet ldflags stamp (tasks.md 5.2) is not yet in .goreleaser.yaml; until it lands on the packaging side the repair path stays dormant and this PR changes nothing user-visible.
  • Once both branches are in, alias bundledBinaryBaseName from extension.BundledBinaryName (TODO left on the constant).

Testing

  • Unit: internal/update covers every behaviour above, including the Windows path from any host via a goos parameter. All new behaviour was written test-first.
  • Integration: transition repair, complete-set no-op, pre-bundling release unaffected, broken-archive fail-loud, leftover cleanup, plus the existing mock-GitHub happy/failure paths.
  • Manual testbed against packages built by the dpx-692 pipeline (goreleaser, npm publisher): the full transition (published 0.23.0 to bundling release, repair, no-op, bundled-to-bundled upgrade, rollback, forward) passes on the binary channel and npm. Caveat: the currently published bundle predates the bundled-extensions list contract, so the release gate was run as a warning for that exercise; Homebrew was not executed.
Docs

No documentation work needed outside this PR. docs/structured-output.md is updated for the new repairBundled key, and the update guarantees are documented in the internal/update package comment. User-facing bundling docs (docs/extensions-bundling.md) live on dpx-692.

Review

Human review advisable. This is the update path, the one thing a broken release cannot ship a fix for, and it adds new user-visible behaviour (the repair). Not a self-merge candidate.

Closes DPX-693

Carlos Arilla and others added 2 commits September 2, 2026 12:06
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@carillan81 carillan81 added semver: minor docs: skip Pull request does not require documentation changes labels Sep 3, 2026
@carillan81
carillan81 marked this pull request as ready for review September 3, 2026 18:08
@carillan81
carillan81 requested review from a team and peter-smith-phd as code owners September 3, 2026 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs: skip Pull request does not require documentation changes semver: minor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant