Repository navigation
Change an installed plugin's update range in place with bb plugin track - #6043
Open
alexandre-vl wants to merge 31 commits into
Open
alexandre-vl wants to merge 31 commits into
alexandre-vl wants to merge 31 commits into
Conversation
This was referenced Oct 21, 2026
…eviewed release Update checks now name the newest stable release that the installed range excludes (outsideRange, with a breaking flag for a new major or a new 0.x minor) and flag in-range candidates that cross the same boundary (candidateBreaking). Both are computed from the tag list or packument the check already fetched and are exposed on InstalledPlugin.updateState with the candidate's display. Resolvers never offer a release older than the installed one: a git range stops at the first tag that is not newer, an npm range or default spec answers current, and a dist-tag that moved below the installed version answers current with a detail saying bb does not downgrade. The apply request accepts expectedVersion; the server refuses, without touching the installation, unless the freshly resolved candidate matches it. Update jobs only join an active job that applies the same reviewed version. A full check isolates each plugin: one that throws is persisted as unavailable with the error as its detail, and the others are still checked. A successful apply keeps a still-newer outside-range release on the entry.
applyUpdate and experimental_startUpdate accept an optional expectedVersion and send it in the apply request, so the server can refuse to activate a different release than the one the caller confirmed. Omitting it keeps the previous behaviour of applying the newest compatible release in range.
…release bb plugin outdated gains an Outside range column (the version, marked "may break" across a major or a 0.x minor) and a note that its compatibility is not checked; Status marks a breaking in-range candidate. bb plugin update sends the checked candidate as expectedVersion, warns before confirming a breaking candidate, mentions a release outside the range, and explains a current plugin as "current within <range>" when a newer release is excluded.
…wed release The plugin detail release status names a newer release that the installed range excludes (with a "may include breaking changes" note and "Compatibility not checked") when there is no in-range update, and marks an available release that may break. The update dialog names a git release by its tag rather than a short commit, shows the full release display in its details, and warns before applying a breaking release. Update and Retry send the reviewed version as expectedVersion so the server refuses a different one.
The plugins guide explains that updates stay inside the installed range and never downgrade, that outdated, update, and the plugin page name a newer release outside the range without checking its compatibility, and that the SDK update calls accept expectedVersion. The update-jobs audit entry records the expectedVersion contract and job deduplication by expected version.
When a plugin has no in-range update but a newer release exists outside its installed range, the installed-plugins row shows a muted "<version> outside range" hint in its footer, with a hover explanation naming the range, whether the release may include breaking changes, and that compatibility is not checked. The hint is plain text, not an Update control, and an in-range update, a failed update, a runtime status, or a check that needs attention still take the row's single signal.
BB servers before reviewed-release updates reject any update request field with a 400 "expected an empty JSON object". When applyUpdate or experimental_startUpdate sent expectedVersion and gets that rejection, the SDK now throws an error saying the update was not started because the server predates reviewed-release updates, instead of the opaque HTTP 400. Other rejections still surface as the server reported them.
The bundled declarations now type applyUpdate as applyUpdate(args: PluginApplyUpdateArgs), so the declaration test looks for that signature instead of the old PluginIdArgs one.
…ange skips A release is breaking when it changes the major version, the minor version before 1.0, or the patch version before 0.1, which is what a caret range excludes; 0.0.x patches were not flagged before. The release reported outside the installed range is now the newest stable release newer than the installed one that the range does not admit, so a disjoint range such as ^1.0.0 || ^3.0.0 names 2.0.0 instead of dropping the report because 3.0.0 satisfies it. Tests cover both, and an incompatible npm release older than the installed one staying out of the blocked report. The update dialog, the CLI breaking warning, and the plugins guide describe the 0.0.x rule.
A current check can carry a detail, such as an npm dist-tag that moved below
the installed version ("bb does not downgrade"). The plugin list now forwards
that detail for current outcomes as it already did for unavailable ones. The
plugin page shows it as a note under Update, and a detail alone now opens that
section, which also makes an unavailable check's detail visible there.
`bb plugin outdated` prints one "<id>: <detail>" line under the table for
every result that has a detail.
Both `bb plugin update` lines that name a release outside the installed
range ("current within <range>; <version> is outside the range" and
"<version> is outside the range <range> and is not part of this update") now
add "(may break)" when that release is breaking, as `bb plugin outdated`
already did. The plugins guide says a refused reviewed-release update leaves
the installation untouched while the fresh check result is still saved.
…updates The SDK now raises a typed PluginUpdateExpectedVersionUnsupportedError, exported from @bb/sdk with the plugin id and expected version, when a server rejects expectedVersion with its old "expected an empty JSON object" 400. SDK callers keep the message that suggests retrying without expectedVersion. `bb plugin update`, which always sends the reviewed version, recognizes the error by type in both the waiting and --no-wait paths and tells the user to upgrade bb on the server and run the update again.
The update-jobs audit entry now names applyUpdate.expectedVersion as a plugin-visible field on the stable applyUpdate member, describes its refusal and job deduplication, the CLI and app callers that send it through the same route, the typed SDK error from older servers, and what stabilizing it requires.
The release outside the installed range could be one in a gap of the range even when a newer release the range admits exists. A ^0.1.0||>=0.2.1 install at 0.1.9 with tags v0.2.0 to v0.2.5 named 0.2.0, the release the marketplace entry excludes on purpose, while the check offered 0.2.5, and an install at 1.5.0 tracking >=2.0.0 <3.0.0 named the older 1.8.0 next to 2.1.0. The reported release must now be newer than both the installed version and the newest release that satisfies the range, using semver.satisfies so prerelease handling matches range matching, on the git and npm paths. A gap release is still named when every release the range admits is older than it. When the newest admitted release is incompatible, it stays reported as blocked and nothing is named outside the range.
…n place setInstalledPluginTrackingRange rewrites the source and range columns of a git semver-range or npm row in one compare-and-set UPDATE and clears the persisted update check and last failure, keeping provenance, the resolved release, the enabled flag, and every other column.
POST /api/v1/plugins/:id/tracking-range takes { range, dryRun? } and
returns { applied, previousRange, range, check }. Under the registration and
lifecycle locks it rewrites the source and range of a git semver-range row
or an npm row with a managed artifact, keeping provenance, the catalog link,
the enabled flag, and all plugin data, then checks the plugin again and
persists that check. A dry run or the current range checks a copy of the row
and persists nothing.
The range's whitespace collapses to single spaces before it is validated,
compared, and stored. previousRange is the range, exact version, or dist-tag
the install followed, and null for an npm install that named no version.
It refuses path, builtin, and git ref sources, older npm installs whose
directory depends on their spec, invalid ranges, bare versions, a range that
admits no version, and a range that admits neither the installed release nor
a newer one: <1.0.0 || >=2.0.0 at 1.5.0 is accepted, and <=1.5.0 freezes
1.5.0. A pinned npm update now points at bb plugin track instead of remove
and reinstall, and the source detail reports an npm range.
The SDK member posts the range and dry-run flag to the tracking-range route and parses the result. It is plugin-visible through PluginBbSdk, so it is experimental, listed in docs/api_to_audit.md, in the backend SDK reference, and on the Plugin Guide's bb SDK card.
bb plugin track <id> <range> previews what the range offers, asks for confirmation (--yes skips, non-TTY refuses), then changes the range and reports the new check; --dry-run only previews and --json prints the result. bb plugin update now tells a pinned npm plugin to use bb plugin track, which keeps its settings, secrets, and schedules. The plugins guide chapter and the bb-cli command index document the command, and the guide explains that an install keeps the range it was installed with.
A legacy npm row with an exact spec resolves as pinned, so no update ever moves it to managed storage; its refusal now says so instead of pointing at bb plugin update. Its update check carries the same refusal as the pinned check's detail, and bb plugin update prints that detail instead of suggesting bb plugin track.
The Details section of a git semver-range or npm plugin shows its update range with a Change range action; an npm install without a range shows the dist-tag it follows, its latest release, or None for an exact version. The dialog previews what a range offers through a dry run of plugins.experimental_setTrackingRange, enables the change only for the previewed range, clears a failed change once the range is edited or previewed again, and refreshes the plugin list and source after it applies. The source detail reports npmSpecKind and, for a dist-tag install, distTag, and bb plugin source prints the dist-tag.
The tracking range result gains openEnded, true when some comparator set of the range has no upper bound, so every future major release would be offered. bb plugin track warns in its dry run and before it asks, and the Change range preview shows the warning; both still accept the range.
checkRows records a failed check as unavailable for that plugin, so the range change takes the entry it returns instead of persisting its own. The registration lock now covers only reading the row, planning the change, and writing it; the check after the change runs once it is released, under the lifecycle lock checkRows takes for that plugin. A dry run takes no lock, so a preview neither waits behind an install, update, or removal nor blocks one while it probes the source. The preview, which must not persist, reports a failure with the same failedCheckEntry.
When the update check found a newer release outside the installed range, the dialog opens on >=<installed> <(major of that release + 1).0.0, which always includes it, and says why; the user can edit it before previewing. Previews name releases with the compact release label, mark a candidate that may break, and say when a newer release stays outside the previewed range.
The preview and result lines now carry the update check's may-break flag and the newer release the range still excludes, as bb plugin update does.
A dry run against the proposed range returns the update check's breaking flag and the newest release outside that range.
The distribution guide's Updates section now covers, together, staying inside the installed range without downgrading, applying only the reviewed release, reporting the newest release outside the range, and moving an install to another range with bb plugin track or Change range. The CLI guide drops the range explanation PR-C now gives and mentions the Change range suggestion.
outsideRange gains an optional suggestedRange. When the release is newer than everything the range admits, it is >=<installed> <(major of that release + 1).0.0, where installed is the release bb resolved (the git tag's version or the npm version, never package.json), so the range does not grow; otherwise, such as for a release between two || alternatives, it is <range> || >=<release> <(major + 1).0.0, so no release the range allowed is dropped. It is recomputed when an update carries the release forward, and left out for an install bb plugin track refuses, such as a legacy npm install. The Change range dialog opens on the suggestion, and bb plugin update's outside-range lines and bb plugin outdated's per-plugin footnote end with To follow it: bb plugin track <id> "<suggestedRange>". Double quotes protect <, >, and | in POSIX shells, PowerShell, and cmd.exe. A server that computes no suggestion, or a check such a server saved, leaves the field out: the dialog then opens on the current range and the CLI prints no command.
The plugins guide, the distribution reference, and the audit entry describe the range bb suggests for a release outside the range and show the bb plugin track command with the range in double quotes. They also cover the refusal of a range that admits no version or neither the installed release nor a newer one, legacy npm installs that cannot change in place and get no suggestion, previousRange, whitespace collapsing, and a registration lock that covers only the change itself.
alexandre-vl
force-pushed
the
plugin-track-range
branch
from
October 21, 2026 15:55
b24cfdc to
682bfc0
Compare
alexandre-vl
marked this pull request as ready for review
October 21, 2026 16:09
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Human comments
What was wrong
An install records the semver range it was installed with, and updates never leave it. #6042 makes bb report the release a range excludes. But the only way to follow that release was to remove the plugin and install it again, and removal deletes the plugin's settings, secrets, schedules and providers (
deleteRemovedPluginData). A range change in the marketplace entry reaches new installs only, so existing users of a^0.y.zentry stay on 0.y forever (#5570).What changed
bb plugin track <id> <range>changes the range an install follows. The install keeps its id, settings, secrets, schedules, KV,data.db, enabled state and catalog link; the next update moves it into the new range.Server (
apps/server/src/services/plugins/plugin-tracking-range.ts, routePOST /api/v1/plugins/:id/tracking-range)satisfies || intersects(range, ">" + installed)).<0.1.0at 0.1.0 is refused, and so is an empty range.<=1.5.0still freezes an install at 1.5.0. The resolvers never move an install to an older release anyway; PR-C added that guard.sourceand the range columns change in one guarded UPDATE (packages/db/src/data/plugins.ts). It clears the saved update check and last failure, then runs a fresh check.openEnded: true; the CLI and UI warn about it.outsideRangefrom PR-C gains an optionalsuggestedRange. If the release is above everything the range admits, it is>=<installed> <(major+1).0.0. Otherwise it is the current range|| >=<release> <(major+1).0.0, so nothing the range already allows is dropped. It is omitted for installs thattrackwould refuse.Contract and SDK
pluginTrackingRangeRequestSchema {range, dryRun?}(strict) andpluginTrackingRangeResultSchema {applied, previousRange, range, openEnded, check}.npmSpecKindanddistTag.plugins.experimental_setTrackingRange({pluginId, range, dryRun?}), with an entry indocs/api_to_audit.mdand a symbol in the Plugin Guide's surfaces.CLI
bb plugin track <id> <range> [--dry-run] [--yes] [--json]previews first: the release the new range would offer, whether it may break, and what the range still excludes. It then asks for confirmation.bb plugin outdatedandbb plugin updateend each outside-range line withTo follow it: bb plugin track <id> "<range>".bb plugin sourceshows an npm install's dist-tag.UI
suggestedRange. It previews before it changes anything, and enables Change only for the range it previewed.Docs
bb-guide-plugins.md) andcommand-index.mdcover the command.distribution.mdis rewritten around ranges, reviewed releases andtrack.backend-sdk.mddocuments the SDK call.No daemon wire change;
HOST_DAEMON_PROTOCOL_VERSIONis unchanged.How you verified
Tests added (each fails against the old code; checked by reverting each fix):
plugin-tracking-range.test.ts(server, real migrated DB, real tagged git fixtures and a local npm registry):>=0.1.0 <1.0.0, updates to v0.2.0 and keeps setting, secret, schedule, KV anddata.db.update-resolver.test.ts: the suggestion rule, including||ranges with the release above the range or in a gap, tag prefixes, and the value recomputed after an update.plugin-track.test.tsandplugin-updates.test.ts, including a payload from an older server withoutsuggestedRange.ChangeTrackingRangeDialog.test.tsx: preview-first, stale previews and reset after a refusal.Suites on this branch before its rebase onto
9fad6b625(pnpm exec turbo run …):lint typecheckover every dependent of server-contract, sdk, plugin-sdk, templates, db and plugin-api-docsTasks: 122 successful, 122 totaltest --filter=@bb/serverTests 3687 passed | 1 skipped (3688)test --filter=@bb/appTests 6266 passed | 4 skipped (6270)test --filter=@bb/cliTests 903 passed | 1 skipped (904)testdb, server-contract, sdk, templatestest@get-bb/plugin-sdk, plugin-api-docs, bb-guidenode scripts/check-plugin-forks.mjs plugins/plugin-api-docs plugins/bb-guide2 forkable plugin(s) passed.Each of the 15 commits also typechecks, and passes the tests of the packages it touches, on its own.
Rebase. The stack is rebased onto
9fad6b625, plus one commit that bumps@get-bb/plugin-sdkto 0.6.67: this PR changes the SDK's bundled declarations, and CI's npm version guard refuses a changed package under a published version.git range-diffshows the other 15 commits unchanged, and the guard printsPASS — @get-bb/plugin-sdk@0.6.67 is not on npm yet.End to end, in an isolated dev instance driven through the CLI, the HTTP API and the browser, with a local marketplace and HTTPS git fixtures. 72 checks; all of PR-C's journeys still pass.
bb plugin outdatedrangefix: 0.2.0 (may break) is outside ^0.1.0. To follow it: bb plugin track rangefix ">=0.1.6 <1.0.0"yrangefix now tracks >=0.1.6 <1.0.0 (was ^0.1.0), then a fresh check offering v0.2.0bb plugin update rangefixupdated and activated …@v0.1.6 (cb0cdf897831) → …@v0.2.0 (9d12ac525319)data.dbmarks unchanged; still enabledbb plugin track rangefix ">=2.0.0 <1.0.0">=2.0.0 <1.0.0 admits no version; use a range like ^0.2.0(exit 1)^0.2.0→ ChangeRange fixture now tracks ^0.2.0 Offers v0.2.1. 0.3.0 stays outside this range.Fixes #5570